quake2ts 0.0.556 → 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.
Files changed (31) hide show
  1. package/package.json +3 -1
  2. package/packages/client/dist/browser/index.global.js +17 -17
  3. package/packages/client/dist/browser/index.global.js.map +1 -1
  4. package/packages/client/dist/cjs/index.cjs +5369 -3577
  5. package/packages/client/dist/cjs/index.cjs.map +1 -1
  6. package/packages/client/dist/esm/index.js +5369 -3577
  7. package/packages/client/dist/esm/index.js.map +1 -1
  8. package/packages/client/dist/tsconfig.tsbuildinfo +1 -1
  9. package/packages/client/dist/types/index.d.ts.map +1 -1
  10. package/packages/engine/dist/cjs/index.cjs +2256 -534
  11. package/packages/engine/dist/cjs/index.cjs.map +1 -1
  12. package/packages/engine/dist/esm/index.js +2266 -538
  13. package/packages/engine/dist/esm/index.js.map +1 -1
  14. package/packages/engine/dist/tsconfig.tsbuildinfo +1 -1
  15. package/packages/engine/dist/types/assets/visibilityAnalyzer.d.ts +7 -2
  16. package/packages/engine/dist/types/assets/visibilityAnalyzer.d.ts.map +1 -1
  17. package/packages/engine/dist/types/render/bloom.d.ts +19 -0
  18. package/packages/engine/dist/types/render/bloom.d.ts.map +1 -0
  19. package/packages/engine/dist/types/render/frame.d.ts +2 -0
  20. package/packages/engine/dist/types/render/frame.d.ts.map +1 -1
  21. package/packages/engine/dist/types/render/renderer.d.ts +2 -0
  22. package/packages/engine/dist/types/render/renderer.d.ts.map +1 -1
  23. package/packages/game/dist/tsconfig.tsbuildinfo +1 -1
  24. package/packages/shared/dist/tsconfig.tsbuildinfo +1 -1
  25. package/packages/test-utils/dist/index.cjs +1178 -580
  26. package/packages/test-utils/dist/index.cjs.map +1 -1
  27. package/packages/test-utils/dist/index.d.cts +198 -47
  28. package/packages/test-utils/dist/index.d.ts +198 -47
  29. package/packages/test-utils/dist/index.js +1149 -582
  30. package/packages/test-utils/dist/index.js.map +1 -1
  31. package/packages/tools/dist/tsconfig.tsbuildinfo +1 -1
@@ -1,7 +1,9 @@
1
1
  "use strict";
2
+ var __create = Object.create;
2
3
  var __defProp = Object.defineProperty;
3
4
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
5
  var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
5
7
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
8
  var __export = (target, all) => {
7
9
  for (var name in all)
@@ -15,6 +17,14 @@ var __copyProps = (to, from, except, desc) => {
15
17
  }
16
18
  return to;
17
19
  };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
18
28
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
29
 
20
30
  // src/index.ts
@@ -22,17 +32,33 @@ var index_exports = {};
22
32
  __export(index_exports, {
23
33
  InputInjector: () => InputInjector,
24
34
  MockPointerLock: () => MockPointerLock,
35
+ captureAudioEvents: () => captureAudioEvents,
36
+ captureCanvasDrawCalls: () => captureCanvasDrawCalls,
37
+ captureGameState: () => captureGameState,
25
38
  createBinaryStreamMock: () => createBinaryStreamMock,
26
39
  createBinaryWriterMock: () => createBinaryWriterMock,
40
+ createControlledTimer: () => createControlledTimer,
27
41
  createEntity: () => createEntity,
28
42
  createEntityStateFactory: () => createEntityStateFactory,
29
43
  createGameStateSnapshotFactory: () => createGameStateSnapshotFactory,
44
+ createMockAudioContext: () => createMockAudioContext,
45
+ createMockCanvas: () => createMockCanvas,
46
+ createMockCanvasContext2D: () => createMockCanvasContext2D,
30
47
  createMockEngine: () => createMockEngine,
31
48
  createMockGame: () => createMockGame,
49
+ createMockImage: () => createMockImage,
50
+ createMockImageData: () => createMockImageData,
51
+ createMockIndexedDB: () => createMockIndexedDB,
52
+ createMockLocalStorage: () => createMockLocalStorage,
53
+ createMockPerformance: () => createMockPerformance,
54
+ createMockRAF: () => createMockRAF,
55
+ createMockSessionStorage: () => createMockSessionStorage,
32
56
  createMockWebGL2Context: () => createMockWebGL2Context,
33
57
  createNetChanMock: () => createNetChanMock,
34
58
  createPlayerStateFactory: () => createPlayerStateFactory,
59
+ createPlaywrightTestClient: () => createPlaywrightTestClient,
35
60
  createSpawnContext: () => createSpawnContext,
61
+ createStorageTestScenario: () => createStorageTestScenario,
36
62
  createTestContext: () => createTestContext,
37
63
  intersects: () => import_shared3.intersects,
38
64
  ladderTrace: () => import_shared3.ladderTrace,
@@ -44,417 +70,271 @@ __export(index_exports, {
44
70
  makeNode: () => makeNode,
45
71
  makePlane: () => makePlane,
46
72
  setupBrowserEnvironment: () => setupBrowserEnvironment,
73
+ setupMockAudioContext: () => setupMockAudioContext,
47
74
  setupNodeEnvironment: () => setupNodeEnvironment,
75
+ simulateFrames: () => simulateFrames,
76
+ simulateFramesWithMock: () => simulateFramesWithMock,
48
77
  stairTrace: () => import_shared3.stairTrace,
49
- teardownBrowserEnvironment: () => teardownBrowserEnvironment
78
+ teardownBrowserEnvironment: () => teardownBrowserEnvironment,
79
+ teardownMockAudioContext: () => teardownMockAudioContext,
80
+ waitForGameReady: () => waitForGameReady
50
81
  });
51
82
  module.exports = __toCommonJS(index_exports);
52
83
 
53
- // src/shared/mocks.ts
54
- var import_vitest = require("vitest");
55
- var createBinaryWriterMock = () => ({
56
- writeByte: import_vitest.vi.fn(),
57
- writeShort: import_vitest.vi.fn(),
58
- writeLong: import_vitest.vi.fn(),
59
- writeString: import_vitest.vi.fn(),
60
- writeBytes: import_vitest.vi.fn(),
61
- getBuffer: import_vitest.vi.fn(() => new Uint8Array(0)),
62
- reset: import_vitest.vi.fn(),
63
- // Legacy methods (if any)
64
- writeInt8: import_vitest.vi.fn(),
65
- writeUint8: import_vitest.vi.fn(),
66
- writeInt16: import_vitest.vi.fn(),
67
- writeUint16: import_vitest.vi.fn(),
68
- writeInt32: import_vitest.vi.fn(),
69
- writeUint32: import_vitest.vi.fn(),
70
- writeFloat: import_vitest.vi.fn(),
71
- getData: import_vitest.vi.fn(() => new Uint8Array(0))
72
- });
73
- var createNetChanMock = () => ({
74
- qport: 1234,
75
- // Sequencing
76
- incomingSequence: 0,
77
- outgoingSequence: 0,
78
- incomingAcknowledged: 0,
79
- // Reliable messaging
80
- incomingReliableAcknowledged: false,
81
- incomingReliableSequence: 0,
82
- outgoingReliableSequence: 0,
83
- reliableMessage: createBinaryWriterMock(),
84
- reliableLength: 0,
85
- // Fragmentation
86
- fragmentSendOffset: 0,
87
- fragmentBuffer: null,
88
- fragmentLength: 0,
89
- fragmentReceived: 0,
90
- // Timing
91
- lastReceived: 0,
92
- lastSent: 0,
93
- remoteAddress: { type: "IP", port: 1234 },
94
- // Methods
95
- setup: import_vitest.vi.fn(),
96
- reset: import_vitest.vi.fn(),
97
- transmit: import_vitest.vi.fn(),
98
- process: import_vitest.vi.fn(),
99
- canSendReliable: import_vitest.vi.fn(() => true),
100
- writeReliableByte: import_vitest.vi.fn(),
101
- writeReliableShort: import_vitest.vi.fn(),
102
- writeReliableLong: import_vitest.vi.fn(),
103
- writeReliableString: import_vitest.vi.fn(),
104
- getReliableData: import_vitest.vi.fn(() => new Uint8Array(0)),
105
- needsKeepalive: import_vitest.vi.fn(() => false),
106
- isTimedOut: import_vitest.vi.fn(() => false)
107
- });
108
- var createBinaryStreamMock = () => ({
109
- getPosition: import_vitest.vi.fn(() => 0),
110
- getReadPosition: import_vitest.vi.fn(() => 0),
111
- getLength: import_vitest.vi.fn(() => 0),
112
- getRemaining: import_vitest.vi.fn(() => 0),
113
- seek: import_vitest.vi.fn(),
114
- setReadPosition: import_vitest.vi.fn(),
115
- hasMore: import_vitest.vi.fn(() => true),
116
- hasBytes: import_vitest.vi.fn((amount) => true),
117
- readChar: import_vitest.vi.fn(() => 0),
118
- readByte: import_vitest.vi.fn(() => 0),
119
- readShort: import_vitest.vi.fn(() => 0),
120
- readUShort: import_vitest.vi.fn(() => 0),
121
- readLong: import_vitest.vi.fn(() => 0),
122
- readULong: import_vitest.vi.fn(() => 0),
123
- readFloat: import_vitest.vi.fn(() => 0),
124
- readString: import_vitest.vi.fn(() => ""),
125
- readStringLine: import_vitest.vi.fn(() => ""),
126
- readCoord: import_vitest.vi.fn(() => 0),
127
- readAngle: import_vitest.vi.fn(() => 0),
128
- readAngle16: import_vitest.vi.fn(() => 0),
129
- readData: import_vitest.vi.fn((length) => new Uint8Array(length)),
130
- readPos: import_vitest.vi.fn(),
131
- readDir: import_vitest.vi.fn()
132
- });
84
+ // src/setup/browser.ts
85
+ var import_jsdom = require("jsdom");
86
+ var import_canvas = require("@napi-rs/canvas");
87
+ var import_auto = require("fake-indexeddb/auto");
133
88
 
134
- // src/shared/bsp.ts
135
- var import_shared = require("@quake2ts/shared");
136
- function makePlane(normal, dist) {
137
- return {
138
- normal,
139
- dist,
140
- type: Math.abs(normal.x) === 1 ? 0 : Math.abs(normal.y) === 1 ? 1 : Math.abs(normal.z) === 1 ? 2 : 3,
141
- signbits: (0, import_shared.computePlaneSignBits)(normal)
142
- };
143
- }
144
- function makeAxisBrush(size, contents = import_shared.CONTENTS_SOLID) {
145
- const half = size / 2;
146
- const planes = [
147
- makePlane({ x: 1, y: 0, z: 0 }, half),
148
- makePlane({ x: -1, y: 0, z: 0 }, half),
149
- makePlane({ x: 0, y: 1, z: 0 }, half),
150
- makePlane({ x: 0, y: -1, z: 0 }, half),
151
- makePlane({ x: 0, y: 0, z: 1 }, half),
152
- makePlane({ x: 0, y: 0, z: -1 }, half)
153
- ];
154
- return {
155
- contents,
156
- sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
157
- };
158
- }
159
- function makeNode(plane, children) {
160
- return { plane, children };
161
- }
162
- function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
163
- return {
164
- planes,
165
- nodes,
166
- leaves,
167
- brushes,
168
- leafBrushes,
169
- bmodels: []
170
- };
171
- }
172
- function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
173
- return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
174
- }
175
- function makeLeafModel(brushes) {
176
- const planes = brushes.flatMap((brush) => brush.sides.map((side) => side.plane));
177
- return {
178
- planes,
179
- nodes: [],
180
- leaves: [makeLeaf(0, 0, brushes.length)],
181
- brushes,
182
- leafBrushes: brushes.map((_, i) => i),
183
- bmodels: []
184
- };
185
- }
186
- function makeBrushFromMinsMaxs(mins, maxs, contents = import_shared.CONTENTS_SOLID) {
187
- const planes = [
188
- makePlane({ x: 1, y: 0, z: 0 }, maxs.x),
189
- makePlane({ x: -1, y: 0, z: 0 }, -mins.x),
190
- makePlane({ x: 0, y: 1, z: 0 }, maxs.y),
191
- makePlane({ x: 0, y: -1, z: 0 }, -mins.y),
192
- makePlane({ x: 0, y: 0, z: 1 }, maxs.z),
193
- makePlane({ x: 0, y: 0, z: -1 }, -mins.z)
194
- ];
195
- return {
196
- contents,
197
- sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
198
- };
199
- }
89
+ // src/e2e/input.ts
90
+ var MockPointerLock = class {
91
+ static setup(doc) {
92
+ let _pointerLockElement = null;
93
+ Object.defineProperty(doc, "pointerLockElement", {
94
+ get: () => _pointerLockElement,
95
+ configurable: true
96
+ });
97
+ doc.exitPointerLock = () => {
98
+ if (_pointerLockElement) {
99
+ _pointerLockElement = null;
100
+ doc.dispatchEvent(new Event("pointerlockchange"));
101
+ }
102
+ };
103
+ global.HTMLElement.prototype.requestPointerLock = function() {
104
+ _pointerLockElement = this;
105
+ doc.dispatchEvent(new Event("pointerlockchange"));
106
+ };
107
+ }
108
+ };
109
+ var InputInjector = class {
110
+ constructor(doc, win) {
111
+ this.doc = doc;
112
+ this.win = win;
113
+ }
114
+ keyDown(key, code) {
115
+ const event = new this.win.KeyboardEvent("keydown", {
116
+ key,
117
+ code: code || key,
118
+ bubbles: true,
119
+ cancelable: true,
120
+ view: this.win
121
+ });
122
+ this.doc.dispatchEvent(event);
123
+ }
124
+ keyUp(key, code) {
125
+ const event = new this.win.KeyboardEvent("keyup", {
126
+ key,
127
+ code: code || key,
128
+ bubbles: true,
129
+ cancelable: true,
130
+ view: this.win
131
+ });
132
+ this.doc.dispatchEvent(event);
133
+ }
134
+ mouseMove(movementX, movementY, clientX = 0, clientY = 0) {
135
+ const event = new this.win.MouseEvent("mousemove", {
136
+ bubbles: true,
137
+ cancelable: true,
138
+ view: this.win,
139
+ clientX,
140
+ clientY,
141
+ movementX,
142
+ // Note: JSDOM might not support this standard property fully on event init
143
+ movementY
144
+ });
145
+ Object.defineProperty(event, "movementX", { value: movementX });
146
+ Object.defineProperty(event, "movementY", { value: movementY });
147
+ const target = this.doc.pointerLockElement || this.doc;
148
+ target.dispatchEvent(event);
149
+ }
150
+ mouseDown(button = 0) {
151
+ const event = new this.win.MouseEvent("mousedown", {
152
+ button,
153
+ bubbles: true,
154
+ cancelable: true,
155
+ view: this.win
156
+ });
157
+ const target = this.doc.pointerLockElement || this.doc;
158
+ target.dispatchEvent(event);
159
+ }
160
+ mouseUp(button = 0) {
161
+ const event = new this.win.MouseEvent("mouseup", {
162
+ button,
163
+ bubbles: true,
164
+ cancelable: true,
165
+ view: this.win
166
+ });
167
+ const target = this.doc.pointerLockElement || this.doc;
168
+ target.dispatchEvent(event);
169
+ }
170
+ wheel(deltaY) {
171
+ const event = new this.win.WheelEvent("wheel", {
172
+ deltaY,
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
+ };
200
181
 
201
- // src/game/factories.ts
202
- var createPlayerStateFactory = (overrides) => ({
203
- pm_type: 0,
204
- pm_time: 0,
205
- pm_flags: 0,
206
- origin: { x: 0, y: 0, z: 0 },
207
- velocity: { x: 0, y: 0, z: 0 },
208
- viewAngles: { x: 0, y: 0, z: 0 },
209
- onGround: false,
210
- waterLevel: 0,
211
- watertype: 0,
212
- mins: { x: 0, y: 0, z: 0 },
213
- maxs: { x: 0, y: 0, z: 0 },
214
- damageAlpha: 0,
215
- damageIndicators: [],
216
- blend: [0, 0, 0, 0],
217
- stats: [],
218
- kick_angles: { x: 0, y: 0, z: 0 },
219
- kick_origin: { x: 0, y: 0, z: 0 },
220
- gunoffset: { x: 0, y: 0, z: 0 },
221
- gunangles: { x: 0, y: 0, z: 0 },
222
- gunindex: 0,
223
- gun_frame: 0,
224
- rdflags: 0,
225
- fov: 90,
226
- renderfx: 0,
227
- ...overrides
228
- });
229
- var createEntityStateFactory = (overrides) => ({
230
- number: 0,
231
- origin: { x: 0, y: 0, z: 0 },
232
- angles: { x: 0, y: 0, z: 0 },
233
- oldOrigin: { x: 0, y: 0, z: 0 },
234
- modelIndex: 0,
235
- modelIndex2: 0,
236
- modelIndex3: 0,
237
- modelIndex4: 0,
238
- frame: 0,
239
- skinNum: 0,
240
- effects: 0,
241
- renderfx: 0,
242
- solid: 0,
243
- sound: 0,
244
- event: 0,
245
- ...overrides
246
- });
247
- var createGameStateSnapshotFactory = (overrides) => ({
248
- gravity: { x: 0, y: 0, z: -800 },
249
- origin: { x: 0, y: 0, z: 0 },
250
- velocity: { x: 0, y: 0, z: 0 },
251
- viewangles: { x: 0, y: 0, z: 0 },
252
- level: { timeSeconds: 0, frameNumber: 0, previousTimeSeconds: 0, deltaSeconds: 0.1 },
253
- entities: {
254
- activeCount: 0,
255
- worldClassname: "worldspawn"
256
- },
257
- packetEntities: [],
258
- pmFlags: 0,
259
- pmType: 0,
260
- waterlevel: 0,
261
- watertype: 0,
262
- deltaAngles: { x: 0, y: 0, z: 0 },
263
- health: 100,
264
- armor: 0,
265
- ammo: 0,
266
- blend: [0, 0, 0, 0],
267
- damageAlpha: 0,
268
- damageIndicators: [],
269
- stats: [],
270
- kick_angles: { x: 0, y: 0, z: 0 },
271
- kick_origin: { x: 0, y: 0, z: 0 },
272
- gunoffset: { x: 0, y: 0, z: 0 },
273
- gunangles: { x: 0, y: 0, z: 0 },
274
- gunindex: 0,
275
- pm_time: 0,
276
- gun_frame: 0,
277
- rdflags: 0,
278
- fov: 90,
279
- renderfx: 0,
280
- pm_flags: 0,
281
- pm_type: 0,
282
- ...overrides
283
- });
284
-
285
- // src/game/helpers.ts
286
- var import_vitest2 = require("vitest");
287
- var import_game = require("@quake2ts/game");
288
- var import_shared2 = require("@quake2ts/shared");
289
- var import_shared3 = require("@quake2ts/shared");
290
- var createMockEngine = () => ({
291
- sound: import_vitest2.vi.fn(),
292
- soundIndex: import_vitest2.vi.fn((sound) => 0),
293
- modelIndex: import_vitest2.vi.fn((model) => 0),
294
- centerprintf: import_vitest2.vi.fn()
295
- });
296
- var createMockGame = (seed = 12345) => {
297
- const spawnRegistry = new import_game.SpawnRegistry();
298
- const hooks = new import_game.ScriptHookRegistry();
299
- const game = {
300
- random: (0, import_shared2.createRandomGenerator)({ seed }),
301
- registerEntitySpawn: import_vitest2.vi.fn((classname, spawnFunc) => {
302
- spawnRegistry.register(classname, (entity) => spawnFunc(entity));
303
- }),
304
- unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
305
- spawnRegistry.unregister(classname);
306
- }),
307
- getCustomEntities: import_vitest2.vi.fn(() => Array.from(spawnRegistry.keys())),
308
- hooks,
309
- registerHooks: import_vitest2.vi.fn((newHooks) => hooks.register(newHooks)),
310
- spawnWorld: import_vitest2.vi.fn(() => {
311
- hooks.onMapLoad("q2dm1");
312
- }),
313
- clientBegin: import_vitest2.vi.fn((client) => {
314
- hooks.onPlayerSpawn({});
315
- }),
316
- damage: import_vitest2.vi.fn((amount) => {
317
- hooks.onDamage({}, null, null, amount, 0, 0);
318
- })
319
- };
320
- return { game, spawnRegistry };
321
- };
322
- function createTestContext(options) {
323
- const engine = createMockEngine();
324
- const seed = options?.seed ?? 12345;
325
- const { game, spawnRegistry } = createMockGame(seed);
326
- const traceFn = import_vitest2.vi.fn((start, end, mins, maxs) => ({
327
- fraction: 1,
328
- ent: null,
329
- allsolid: false,
330
- startsolid: false,
331
- endpos: end,
332
- plane: { normal: { x: 0, y: 0, z: 1 }, dist: 0 },
333
- surfaceFlags: 0,
334
- contents: 0
335
- }));
336
- const entityList = options?.initialEntities ? [...options.initialEntities] : [];
337
- const hooks = game.hooks;
338
- const entities = {
339
- spawn: import_vitest2.vi.fn(() => {
340
- const ent = new import_game.Entity(entityList.length + 1);
341
- entityList.push(ent);
342
- hooks.onEntitySpawn(ent);
343
- return ent;
344
- }),
345
- free: import_vitest2.vi.fn((ent) => {
346
- const idx = entityList.indexOf(ent);
347
- if (idx !== -1) {
348
- entityList.splice(idx, 1);
349
- }
350
- hooks.onEntityRemove(ent);
351
- }),
352
- finalizeSpawn: import_vitest2.vi.fn(),
353
- freeImmediate: import_vitest2.vi.fn((ent) => {
354
- const idx = entityList.indexOf(ent);
355
- if (idx !== -1) {
356
- entityList.splice(idx, 1);
357
- }
358
- }),
359
- setSpawnRegistry: import_vitest2.vi.fn(),
360
- timeSeconds: 10,
361
- deltaSeconds: 0.1,
362
- modelIndex: import_vitest2.vi.fn(() => 0),
363
- scheduleThink: import_vitest2.vi.fn((entity, time) => {
364
- entity.nextthink = time;
365
- }),
366
- linkentity: import_vitest2.vi.fn(),
367
- trace: traceFn,
368
- pointcontents: import_vitest2.vi.fn(() => 0),
369
- multicast: import_vitest2.vi.fn(),
370
- unicast: import_vitest2.vi.fn(),
371
- engine,
372
- game,
373
- sound: import_vitest2.vi.fn((ent, chan, sound, vol, attn, timeofs) => {
374
- engine.sound(ent, chan, sound, vol, attn, timeofs);
375
- }),
376
- soundIndex: import_vitest2.vi.fn((sound) => engine.soundIndex(sound)),
377
- useTargets: import_vitest2.vi.fn((entity, activator) => {
378
- }),
379
- findByTargetName: import_vitest2.vi.fn(() => []),
380
- pickTarget: import_vitest2.vi.fn(() => null),
381
- killBox: import_vitest2.vi.fn(),
382
- rng: (0, import_shared2.createRandomGenerator)({ seed }),
383
- imports: {
384
- configstring: import_vitest2.vi.fn(),
385
- trace: traceFn,
386
- pointcontents: import_vitest2.vi.fn(() => 0)
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: () => {
387
213
  },
388
- level: {
389
- intermission_angle: { x: 0, y: 0, z: 0 },
390
- intermission_origin: { x: 0, y: 0, z: 0 },
391
- next_auto_save: 0,
392
- health_bar_entities: null
214
+ compileShader: () => {
393
215
  },
394
- targetNameIndex: /* @__PURE__ */ new Map(),
395
- forEachEntity: import_vitest2.vi.fn((callback) => {
396
- entityList.forEach(callback);
397
- }),
398
- find: import_vitest2.vi.fn((predicate) => {
399
- return entityList.find(predicate);
400
- }),
401
- findByClassname: import_vitest2.vi.fn((classname) => {
402
- return entityList.find((e) => e.classname === classname);
403
- }),
404
- beginFrame: import_vitest2.vi.fn((timeSeconds) => {
405
- entities.timeSeconds = timeSeconds;
406
- }),
407
- targetAwareness: {
408
- timeSeconds: 10,
409
- frameNumber: 1,
410
- sightEntity: null,
411
- soundEntity: null
216
+ getShaderParameter: (_, param) => {
217
+ if (param === 35713) return true;
218
+ return true;
412
219
  },
413
- // Adding missing properties to satisfy EntitySystem interface partially or fully
414
- // We cast to unknown first anyway, but filling these in makes it safer for consumers
415
- skill: 1,
416
- deathmatch: false,
417
- coop: false,
418
- activeCount: entityList.length,
419
- world: entityList.find((e) => e.classname === "worldspawn") || new import_game.Entity(0)
420
- // ... other EntitySystem properties would go here
421
- };
422
- return {
423
- keyValues: {},
424
- entities,
425
- game,
426
- engine,
427
- health_multiplier: 1,
428
- warn: import_vitest2.vi.fn(),
429
- free: import_vitest2.vi.fn(),
430
- // Mock precache functions if they are part of SpawnContext in future or TestContext extensions
431
- precacheModel: import_vitest2.vi.fn(),
432
- precacheSound: import_vitest2.vi.fn(),
433
- precacheImage: import_vitest2.vi.fn()
434
- };
435
- }
436
- function createSpawnContext() {
437
- return createTestContext();
438
- }
439
- function createEntity() {
440
- return new import_game.Entity(1);
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;
441
304
  }
442
305
 
443
306
  // src/setup/browser.ts
444
- var import_jsdom = require("jsdom");
445
- var import_canvas = require("@napi-rs/canvas");
446
- var import_auto = require("fake-indexeddb/auto");
447
307
  function setupBrowserEnvironment(options = {}) {
448
- const { url = "http://localhost", pretendToBeVisual = true } = options;
308
+ const {
309
+ url = "http://localhost",
310
+ pretendToBeVisual = true,
311
+ resources = void 0,
312
+ enableWebGL2 = false,
313
+ enablePointerLock = false
314
+ } = options;
449
315
  const dom = new import_jsdom.JSDOM("<!DOCTYPE html><html><head></head><body></body></html>", {
450
316
  url,
451
- pretendToBeVisual
317
+ pretendToBeVisual,
318
+ resources
452
319
  });
453
320
  global.window = dom.window;
454
321
  global.document = dom.window.document;
455
- global.navigator = dom.window.navigator;
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
+ }
456
335
  global.location = dom.window.location;
457
336
  global.HTMLElement = dom.window.HTMLElement;
337
+ global.HTMLCanvasElement = dom.window.HTMLCanvasElement;
458
338
  global.Event = dom.window.Event;
459
339
  global.CustomEvent = dom.window.CustomEvent;
460
340
  global.DragEvent = dom.window.DragEvent;
@@ -507,6 +387,9 @@ function setupBrowserEnvironment(options = {}) {
507
387
  if (contextId === "2d") {
508
388
  return napiCanvas.getContext("2d", options3);
509
389
  }
390
+ if (enableWebGL2 && contextId === "webgl2") {
391
+ return createMockWebGL2Context(domCanvas);
392
+ }
510
393
  if (contextId === "webgl" || contextId === "webgl2") {
511
394
  return originalGetContext(contextId, options3);
512
395
  }
@@ -517,6 +400,15 @@ function setupBrowserEnvironment(options = {}) {
517
400
  }
518
401
  return originalCreateElement(tagName, options2);
519
402
  };
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
+ }
520
412
  global.Image = import_canvas.Image;
521
413
  global.ImageData = import_canvas.ImageData;
522
414
  if (typeof global.createImageBitmap === "undefined") {
@@ -543,6 +435,24 @@ function setupBrowserEnvironment(options = {}) {
543
435
  return Buffer.from(str, "base64").toString("binary");
544
436
  };
545
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
+ }
546
456
  }
547
457
  function teardownBrowserEnvironment() {
548
458
  delete global.window;
@@ -551,9 +461,100 @@ function teardownBrowserEnvironment() {
551
461
  delete global.localStorage;
552
462
  delete global.location;
553
463
  delete global.HTMLElement;
464
+ delete global.HTMLCanvasElement;
554
465
  delete global.Image;
555
466
  delete global.ImageData;
556
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
+ }
478
+
479
+ // src/setup/canvas.ts
480
+ var import_canvas2 = require("@napi-rs/canvas");
481
+ function createMockCanvas(width = 300, height = 150) {
482
+ if (typeof document !== "undefined" && document.createElement) {
483
+ const canvas2 = document.createElement("canvas");
484
+ canvas2.width = width;
485
+ canvas2.height = height;
486
+ return canvas2;
487
+ }
488
+ const canvas = new import_canvas2.Canvas(width, height);
489
+ const originalGetContext = canvas.getContext.bind(canvas);
490
+ canvas.getContext = function(contextId, options) {
491
+ if (contextId === "webgl2") {
492
+ return createMockWebGL2Context(canvas);
493
+ }
494
+ if (contextId === "2d") {
495
+ return originalGetContext("2d", options);
496
+ }
497
+ return originalGetContext(contextId, options);
498
+ };
499
+ return canvas;
500
+ }
501
+ function createMockCanvasContext2D(canvas) {
502
+ if (!canvas) {
503
+ canvas = createMockCanvas();
504
+ }
505
+ return canvas.getContext("2d");
506
+ }
507
+ function captureCanvasDrawCalls(context) {
508
+ const drawCalls = [];
509
+ const methodsToSpy = [
510
+ "fillRect",
511
+ "strokeRect",
512
+ "clearRect",
513
+ "fillText",
514
+ "strokeText",
515
+ "drawImage",
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;
538
+ }
539
+ function createMockImageData(width, height, fillColor) {
540
+ const imageData = new import_canvas2.ImageData(width, height);
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;
551
+ }
552
+ function createMockImage(width, height, src) {
553
+ const img = new import_canvas2.Image();
554
+ if (width) img.width = width;
555
+ if (height) img.height = height;
556
+ if (src) img.src = src;
557
+ return img;
557
558
  }
558
559
 
559
560
  // src/setup/node.ts
@@ -562,237 +563,829 @@ function setupNodeEnvironment() {
562
563
  }
563
564
  }
564
565
 
565
- // src/setup/webgl.ts
566
- function createMockWebGL2Context(canvas) {
567
- const gl = {
568
- canvas,
569
- drawingBufferWidth: canvas.width,
570
- drawingBufferHeight: canvas.height,
571
- // Constants
572
- VERTEX_SHADER: 35633,
573
- FRAGMENT_SHADER: 35632,
574
- COMPILE_STATUS: 35713,
575
- LINK_STATUS: 35714,
576
- ARRAY_BUFFER: 34962,
577
- ELEMENT_ARRAY_BUFFER: 34963,
578
- STATIC_DRAW: 35044,
579
- DYNAMIC_DRAW: 35048,
580
- FLOAT: 5126,
581
- DEPTH_TEST: 2929,
582
- BLEND: 3042,
583
- SRC_ALPHA: 770,
584
- ONE_MINUS_SRC_ALPHA: 771,
585
- TEXTURE_2D: 3553,
586
- RGBA: 6408,
587
- UNSIGNED_BYTE: 5121,
588
- COLOR_BUFFER_BIT: 16384,
589
- DEPTH_BUFFER_BIT: 256,
590
- TRIANGLES: 4,
591
- TRIANGLE_STRIP: 5,
592
- TRIANGLE_FAN: 6,
593
- // Methods
594
- createShader: () => ({}),
595
- shaderSource: () => {
596
- },
597
- compileShader: () => {
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));
598
596
  },
599
- getShaderParameter: (_, param) => {
600
- if (param === 35713) return true;
601
- return true;
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 () => {
602
636
  },
603
- getShaderInfoLog: () => "",
604
- createProgram: () => ({}),
605
- attachShader: () => {
637
+ suspend: async () => {
606
638
  },
607
- linkProgram: () => {
639
+ close: async () => {
608
640
  },
609
- getProgramParameter: (_, param) => {
610
- if (param === 35714) return true;
611
- return true;
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
+
679
+ // src/setup/timing.ts
680
+ var activeMockRAF;
681
+ function createMockRAF() {
682
+ let callbacks = [];
683
+ let nextId = 1;
684
+ let currentTime = 0;
685
+ const originalRAF = global.requestAnimationFrame;
686
+ const originalCancelRAF = global.cancelAnimationFrame;
687
+ const raf = (callback) => {
688
+ const id = nextId++;
689
+ callbacks.push({ id, callback });
690
+ return id;
691
+ };
692
+ const cancel = (id) => {
693
+ callbacks = callbacks.filter((cb) => cb.id !== id);
694
+ };
695
+ const mock = {
696
+ tick(timestamp) {
697
+ if (typeof timestamp !== "number") {
698
+ currentTime += 16.6;
699
+ } else {
700
+ currentTime = timestamp;
701
+ }
702
+ const currentCallbacks = [...callbacks];
703
+ callbacks = [];
704
+ currentCallbacks.forEach(({ callback }) => {
705
+ callback(currentTime);
706
+ });
612
707
  },
613
- getProgramInfoLog: () => "",
614
- useProgram: () => {
708
+ advance(deltaMs = 16.6) {
709
+ this.tick(currentTime + deltaMs);
615
710
  },
616
- createBuffer: () => ({}),
617
- bindBuffer: () => {
711
+ getCallbacks() {
712
+ return callbacks.map((c) => c.callback);
618
713
  },
619
- bufferData: () => {
714
+ reset() {
715
+ callbacks = [];
716
+ nextId = 1;
717
+ currentTime = 0;
620
718
  },
621
- enableVertexAttribArray: () => {
719
+ enable() {
720
+ activeMockRAF = this;
721
+ global.requestAnimationFrame = raf;
722
+ global.cancelAnimationFrame = cancel;
622
723
  },
623
- vertexAttribPointer: () => {
724
+ disable() {
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
624
749
  },
625
- enable: () => {
750
+ clearMarks: () => {
626
751
  },
627
- disable: () => {
752
+ clearMeasures: () => {
628
753
  },
629
- depthMask: () => {
754
+ clearResourceTimings: () => {
630
755
  },
631
- blendFunc: () => {
756
+ getEntries: () => [],
757
+ getEntriesByName: () => [],
758
+ getEntriesByType: () => [],
759
+ mark: () => {
632
760
  },
633
- viewport: () => {
761
+ measure: () => {
634
762
  },
635
- clearColor: () => {
763
+ setResourceTimingBufferSize: () => {
636
764
  },
637
- clear: () => {
765
+ toJSON: () => ({}),
766
+ addEventListener: () => {
638
767
  },
639
- createTexture: () => ({}),
640
- bindTexture: () => {
768
+ removeEventListener: () => {
641
769
  },
642
- texImage2D: () => {
770
+ dispatchEvent: () => true
771
+ };
772
+ mockPerf.advance = (deltaMs) => {
773
+ currentTime += deltaMs;
774
+ };
775
+ mockPerf.setTime = (time) => {
776
+ currentTime = time;
777
+ };
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);
643
811
  },
644
- texParameteri: () => {
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;
645
835
  },
646
- activeTexture: () => {
836
+ clear() {
837
+ timers = [];
647
838
  },
648
- uniform1i: () => {
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);
649
943
  },
650
- uniform1f: () => {
651
- },
652
- uniform2f: () => {
653
- },
654
- uniform3f: () => {
655
- },
656
- uniform4f: () => {
657
- },
658
- uniformMatrix4fv: () => {
659
- },
660
- getUniformLocation: () => ({}),
661
- getAttribLocation: () => 0,
662
- drawArrays: () => {
663
- },
664
- drawElements: () => {
665
- },
666
- createVertexArray: () => ({}),
667
- bindVertexArray: () => {
668
- },
669
- deleteShader: () => {
670
- },
671
- deleteProgram: () => {
672
- },
673
- deleteBuffer: () => {
674
- },
675
- deleteTexture: () => {
676
- },
677
- deleteVertexArray: () => {
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
+ };
978
+ }
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
+ };
993
+ }
994
+ function makeNode(plane, children) {
995
+ return { plane, children };
996
+ }
997
+ function makeBspModel(planes, nodes, leaves, brushes, leafBrushes) {
998
+ return {
999
+ planes,
1000
+ nodes,
1001
+ leaves,
1002
+ brushes,
1003
+ leafBrushes,
1004
+ bmodels: []
1005
+ };
1006
+ }
1007
+ function makeLeaf(contents, firstLeafBrush, numLeafBrushes) {
1008
+ return { contents, cluster: 0, area: 0, firstLeafBrush, numLeafBrushes };
1009
+ }
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
+ };
1020
+ }
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
+ ];
1030
+ return {
1031
+ contents,
1032
+ sides: planes.map((plane) => ({ plane, surfaceFlags: 0 }))
1033
+ };
1034
+ }
1035
+
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));
1219
+ }),
1220
+ unregisterEntitySpawn: import_vitest2.vi.fn((classname) => {
1221
+ spawnRegistry.unregister(classname);
1222
+ }),
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");
1228
+ }),
1229
+ clientBegin: import_vitest2.vi.fn((client) => {
1230
+ hooks.onPlayerSpawn({});
1231
+ }),
1232
+ damage: import_vitest2.vi.fn((amount) => {
1233
+ hooks.onDamage({}, null, null, amount, 0, 0);
1234
+ })
1235
+ };
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);
1265
+ }
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);
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)
678
1303
  },
679
- // WebGL2 specific
680
- texImage3D: () => {
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
681
1309
  },
682
- uniformBlockBinding: () => {
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
683
1328
  },
684
- getExtension: () => null
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
1337
+ };
1338
+ return {
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()
685
1350
  };
686
- return gl;
687
1351
  }
688
-
689
- // src/e2e/input.ts
690
- var MockPointerLock = class {
691
- static setup(doc) {
692
- let _pointerLockElement = null;
693
- Object.defineProperty(doc, "pointerLockElement", {
694
- get: () => _pointerLockElement,
695
- configurable: true
696
- });
697
- doc.exitPointerLock = () => {
698
- if (_pointerLockElement) {
699
- _pointerLockElement = null;
700
- doc.dispatchEvent(new Event("pointerlockchange"));
701
- }
702
- };
703
- global.HTMLElement.prototype.requestPointerLock = function() {
704
- _pointerLockElement = this;
705
- doc.dispatchEvent(new Event("pointerlockchange"));
706
- };
707
- }
708
- };
709
- var InputInjector = class {
710
- constructor(doc, win) {
711
- this.doc = doc;
712
- this.win = win;
713
- }
714
- keyDown(key, code) {
715
- const event = new this.win.KeyboardEvent("keydown", {
716
- key,
717
- code: code || key,
718
- bubbles: true,
719
- cancelable: true,
720
- view: this.win
721
- });
722
- this.doc.dispatchEvent(event);
723
- }
724
- keyUp(key, code) {
725
- const event = new this.win.KeyboardEvent("keyup", {
726
- key,
727
- code: code || key,
728
- bubbles: true,
729
- cancelable: true,
730
- view: this.win
731
- });
732
- this.doc.dispatchEvent(event);
733
- }
734
- mouseMove(movementX, movementY, clientX = 0, clientY = 0) {
735
- const event = new this.win.MouseEvent("mousemove", {
736
- bubbles: true,
737
- cancelable: true,
738
- view: this.win,
739
- clientX,
740
- clientY,
741
- movementX,
742
- // Note: JSDOM might not support this standard property fully on event init
743
- movementY
744
- });
745
- Object.defineProperty(event, "movementX", { value: movementX });
746
- Object.defineProperty(event, "movementY", { value: movementY });
747
- const target = this.doc.pointerLockElement || this.doc;
748
- target.dispatchEvent(event);
749
- }
750
- mouseDown(button = 0) {
751
- const event = new this.win.MouseEvent("mousedown", {
752
- button,
753
- bubbles: true,
754
- cancelable: true,
755
- view: this.win
756
- });
757
- const target = this.doc.pointerLockElement || this.doc;
758
- target.dispatchEvent(event);
759
- }
760
- mouseUp(button = 0) {
761
- const event = new this.win.MouseEvent("mouseup", {
762
- button,
763
- bubbles: true,
764
- cancelable: true,
765
- view: this.win
766
- });
767
- const target = this.doc.pointerLockElement || this.doc;
768
- target.dispatchEvent(event);
769
- }
770
- wheel(deltaY) {
771
- const event = new this.win.WheelEvent("wheel", {
772
- deltaY,
773
- bubbles: true,
774
- cancelable: true,
775
- view: this.win
776
- });
777
- const target = this.doc.pointerLockElement || this.doc;
778
- target.dispatchEvent(event);
779
- }
780
- };
1352
+ function createSpawnContext() {
1353
+ return createTestContext();
1354
+ }
1355
+ function createEntity() {
1356
+ return new import_game.Entity(1);
1357
+ }
781
1358
  // Annotate the CommonJS export names for ESM import in node:
782
1359
  0 && (module.exports = {
783
1360
  InputInjector,
784
1361
  MockPointerLock,
1362
+ captureAudioEvents,
1363
+ captureCanvasDrawCalls,
1364
+ captureGameState,
785
1365
  createBinaryStreamMock,
786
1366
  createBinaryWriterMock,
1367
+ createControlledTimer,
787
1368
  createEntity,
788
1369
  createEntityStateFactory,
789
1370
  createGameStateSnapshotFactory,
1371
+ createMockAudioContext,
1372
+ createMockCanvas,
1373
+ createMockCanvasContext2D,
790
1374
  createMockEngine,
791
1375
  createMockGame,
1376
+ createMockImage,
1377
+ createMockImageData,
1378
+ createMockIndexedDB,
1379
+ createMockLocalStorage,
1380
+ createMockPerformance,
1381
+ createMockRAF,
1382
+ createMockSessionStorage,
792
1383
  createMockWebGL2Context,
793
1384
  createNetChanMock,
794
1385
  createPlayerStateFactory,
1386
+ createPlaywrightTestClient,
795
1387
  createSpawnContext,
1388
+ createStorageTestScenario,
796
1389
  createTestContext,
797
1390
  intersects,
798
1391
  ladderTrace,
@@ -804,8 +1397,13 @@ var InputInjector = class {
804
1397
  makeNode,
805
1398
  makePlane,
806
1399
  setupBrowserEnvironment,
1400
+ setupMockAudioContext,
807
1401
  setupNodeEnvironment,
1402
+ simulateFrames,
1403
+ simulateFramesWithMock,
808
1404
  stairTrace,
809
- teardownBrowserEnvironment
1405
+ teardownBrowserEnvironment,
1406
+ teardownMockAudioContext,
1407
+ waitForGameReady
810
1408
  });
811
1409
  //# sourceMappingURL=index.cjs.map