@quake2ts/test-utils 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +454 -0
  2. package/dist/index.cjs +5432 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +2150 -0
  5. package/dist/index.d.ts +2150 -0
  6. package/dist/index.js +5165 -0
  7. package/dist/index.js.map +1 -0
  8. package/package.json +82 -0
  9. package/src/client/helpers/hud.ts +114 -0
  10. package/src/client/helpers/prediction.ts +136 -0
  11. package/src/client/helpers/view.ts +201 -0
  12. package/src/client/mocks/console.ts +75 -0
  13. package/src/client/mocks/download.ts +48 -0
  14. package/src/client/mocks/input.ts +246 -0
  15. package/src/client/mocks/network.ts +148 -0
  16. package/src/client/mocks/state.ts +148 -0
  17. package/src/e2e/network.ts +47 -0
  18. package/src/e2e/playwright.ts +90 -0
  19. package/src/e2e/visual.ts +172 -0
  20. package/src/engine/helpers/pipeline-test-template.ts +113 -0
  21. package/src/engine/helpers/webgpu-rendering.ts +251 -0
  22. package/src/engine/mocks/assets.ts +129 -0
  23. package/src/engine/mocks/audio.ts +152 -0
  24. package/src/engine/mocks/buffers.ts +88 -0
  25. package/src/engine/mocks/lighting.ts +64 -0
  26. package/src/engine/mocks/particles.ts +76 -0
  27. package/src/engine/mocks/renderer.ts +218 -0
  28. package/src/engine/mocks/webgl.ts +267 -0
  29. package/src/engine/mocks/webgpu.ts +262 -0
  30. package/src/engine/rendering.ts +103 -0
  31. package/src/game/factories.ts +204 -0
  32. package/src/game/helpers/physics.ts +171 -0
  33. package/src/game/helpers/save.ts +232 -0
  34. package/src/game/helpers.ts +310 -0
  35. package/src/game/mocks/ai.ts +67 -0
  36. package/src/game/mocks/combat.ts +61 -0
  37. package/src/game/mocks/items.ts +166 -0
  38. package/src/game/mocks.ts +105 -0
  39. package/src/index.ts +93 -0
  40. package/src/server/helpers/bandwidth.ts +127 -0
  41. package/src/server/helpers/multiplayer.ts +158 -0
  42. package/src/server/helpers/snapshot.ts +241 -0
  43. package/src/server/mockNetDriver.ts +106 -0
  44. package/src/server/mockTransport.ts +50 -0
  45. package/src/server/mocks/commands.ts +93 -0
  46. package/src/server/mocks/connection.ts +139 -0
  47. package/src/server/mocks/master.ts +97 -0
  48. package/src/server/mocks/physics.ts +32 -0
  49. package/src/server/mocks/state.ts +162 -0
  50. package/src/server/mocks/transport.ts +161 -0
  51. package/src/setup/audio.ts +118 -0
  52. package/src/setup/browser.ts +249 -0
  53. package/src/setup/canvas.ts +142 -0
  54. package/src/setup/node.ts +21 -0
  55. package/src/setup/storage.ts +60 -0
  56. package/src/setup/timing.ts +142 -0
  57. package/src/setup/webgl.ts +8 -0
  58. package/src/setup/webgpu.ts +113 -0
  59. package/src/shared/bsp.ts +145 -0
  60. package/src/shared/collision.ts +64 -0
  61. package/src/shared/factories.ts +88 -0
  62. package/src/shared/math.ts +65 -0
  63. package/src/shared/mocks.ts +243 -0
  64. package/src/shared/pak-loader.ts +45 -0
  65. package/src/visual/snapshots.ts +292 -0
@@ -0,0 +1,267 @@
1
+ import { vi } from 'vitest';
2
+
3
+ interface ShaderRecord {
4
+ readonly id: number;
5
+ readonly type: GLenum;
6
+ }
7
+
8
+ interface ProgramRecord {
9
+ readonly id: number;
10
+ }
11
+
12
+ export class MockWebGL2RenderingContext {
13
+ readonly ARRAY_BUFFER = 0x8892;
14
+ readonly ELEMENT_ARRAY_BUFFER = 0x8893;
15
+ readonly STATIC_DRAW = 0x88e4;
16
+ readonly DYNAMIC_DRAW = 0x88e8;
17
+ readonly FLOAT = 0x1406;
18
+ readonly UNSIGNED_SHORT = 0x1403;
19
+ readonly TEXTURE_2D = 0x0de1;
20
+ readonly TEXTURE_CUBE_MAP = 0x8513;
21
+ readonly TEXTURE_CUBE_MAP_POSITIVE_X = 0x8515;
22
+ readonly TEXTURE0 = 0x84c0;
23
+ readonly TEXTURE_WRAP_S = 0x2802;
24
+ readonly TEXTURE_WRAP_T = 0x2803;
25
+ readonly TEXTURE_MIN_FILTER = 0x2801;
26
+ readonly TEXTURE_MAG_FILTER = 0x2800;
27
+ readonly LINEAR = 0x2601;
28
+ readonly NEAREST = 0x2600;
29
+ readonly CLAMP_TO_EDGE = 0x812f;
30
+ readonly RGBA = 0x1908;
31
+ readonly UNSIGNED_BYTE = 0x1401;
32
+ readonly FRAMEBUFFER = 0x8d40;
33
+ readonly COLOR_ATTACHMENT0 = 0x8ce0;
34
+ readonly DEPTH_ATTACHMENT = 0x8d00;
35
+ readonly RENDERBUFFER = 0x8d41;
36
+ readonly DEPTH_COMPONENT24 = 0x81a6;
37
+ readonly FRAMEBUFFER_COMPLETE = 0x8cd5;
38
+ readonly TRIANGLES = 0x0004;
39
+ readonly DEPTH_TEST = 0x0b71;
40
+ readonly CULL_FACE = 0x0b44;
41
+ readonly BLEND = 0x0be2;
42
+ readonly SRC_ALPHA = 0x0302;
43
+ readonly ONE_MINUS_SRC_ALPHA = 0x0303;
44
+ readonly ONE = 1;
45
+ readonly BACK = 0x0405;
46
+ readonly LEQUAL = 0x0203;
47
+ readonly VERTEX_SHADER = 0x8b31;
48
+ readonly FRAGMENT_SHADER = 0x8b30;
49
+ readonly COMPILE_STATUS = 0x8b81;
50
+ readonly LINK_STATUS = 0x8b82;
51
+ readonly ONE_MINUS_SRC_COLOR = 0x0301;
52
+ readonly TRIANGLE_STRIP = 0x0005;
53
+ readonly QUERY_RESULT_AVAILABLE = 0x8867;
54
+ readonly QUERY_RESULT = 0x8866;
55
+
56
+ // Constants commonly used in setup/webgl.ts
57
+ readonly TRIANGLE_FAN = 0x0006;
58
+ readonly COLOR_BUFFER_BIT = 0x4000;
59
+ readonly DEPTH_BUFFER_BIT = 0x0100;
60
+
61
+ readonly canvas: HTMLCanvasElement | { width: number; height: number };
62
+ readonly drawingBufferWidth: number;
63
+ readonly drawingBufferHeight: number;
64
+
65
+ private shaderCounter = 0;
66
+ private programCounter = 0;
67
+
68
+ compileSucceeds = true;
69
+ linkSucceeds = true;
70
+ shaderInfoLog = 'shader failed';
71
+ programInfoLog = 'program failed';
72
+
73
+ readonly extensions = new Map<string, unknown>();
74
+ readonly calls: string[] = [];
75
+ readonly uniformLocations = new Map<string, WebGLUniformLocation | null>();
76
+ readonly attributeLocations = new Map<string, number>();
77
+
78
+ constructor(canvas?: HTMLCanvasElement) {
79
+ if (canvas) {
80
+ this.canvas = canvas;
81
+ this.drawingBufferWidth = canvas.width;
82
+ this.drawingBufferHeight = canvas.height;
83
+ } else {
84
+ this.canvas = { width: 800, height: 600 };
85
+ this.drawingBufferWidth = 800;
86
+ this.drawingBufferHeight = 600;
87
+ }
88
+ }
89
+
90
+ enable = vi.fn((cap: GLenum) => this.calls.push(`enable:${cap}`));
91
+ disable = vi.fn((cap: GLenum) => this.calls.push(`disable:${cap}`));
92
+ depthFunc = vi.fn((func: GLenum) => this.calls.push(`depthFunc:${func}`));
93
+ cullFace = vi.fn((mode: GLenum) => this.calls.push(`cullFace:${mode}`));
94
+ depthMask = vi.fn((flag: GLboolean) => this.calls.push(`depthMask:${flag}`));
95
+ blendFuncSeparate = vi.fn((srcRGB: GLenum, dstRGB: GLenum, srcAlpha: GLenum, dstAlpha: GLenum) =>
96
+ this.calls.push(`blendFuncSeparate:${srcRGB}:${dstRGB}:${srcAlpha}:${dstAlpha}`)
97
+ );
98
+ blendFunc = vi.fn((sfactor: GLenum, dfactor: GLenum) => this.calls.push(`blendFunc:${sfactor}:${dfactor}`));
99
+ getExtension = vi.fn((name: string) => this.extensions.get(name) ?? null);
100
+ viewport = vi.fn((x: number, y: number, w: number, h: number) => this.calls.push(`viewport:${x}:${y}:${w}:${h}`));
101
+ clear = vi.fn((mask: number) => this.calls.push(`clear:${mask}`));
102
+ clearColor = vi.fn((r: number, g: number, b: number, a: number) => this.calls.push(`clearColor:${r}:${g}:${b}:${a}`));
103
+
104
+ createShader = vi.fn((type: GLenum) => ({ id: ++this.shaderCounter, type } as unknown as WebGLShader));
105
+ shaderSource = vi.fn((shader: ShaderRecord, source: string) => this.calls.push(`shaderSource:${shader.id}:${source}`));
106
+ compileShader = vi.fn((shader: ShaderRecord) => this.calls.push(`compileShader:${shader.id}`));
107
+ getShaderParameter = vi.fn((shader: ShaderRecord, pname: GLenum) =>
108
+ pname === this.COMPILE_STATUS ? this.compileSucceeds : null
109
+ );
110
+ getShaderInfoLog = vi.fn(() => (this.compileSucceeds ? '' : this.shaderInfoLog));
111
+ deleteShader = vi.fn((shader: ShaderRecord) => this.calls.push(`deleteShader:${shader.id}`));
112
+
113
+ createProgram = vi.fn(() => ({ id: ++this.programCounter } as unknown as WebGLProgram));
114
+ attachShader = vi.fn((program: ProgramRecord, shader: ShaderRecord) =>
115
+ this.calls.push(`attach:${program.id}:${shader.id}`)
116
+ );
117
+ bindAttribLocation = vi.fn((program: ProgramRecord, index: number, name: string) =>
118
+ this.calls.push(`bindAttribLocation:${program.id}:${index}:${name}`)
119
+ );
120
+ linkProgram = vi.fn((program: ProgramRecord) => this.calls.push(`link:${program.id}`));
121
+ getProgramParameter = vi.fn((program: ProgramRecord, pname: GLenum) =>
122
+ pname === this.LINK_STATUS ? this.linkSucceeds : null
123
+ );
124
+ getProgramInfoLog = vi.fn(() => (this.linkSucceeds ? '' : this.programInfoLog));
125
+ deleteProgram = vi.fn((program: ProgramRecord) => this.calls.push(`deleteProgram:${program.id}`));
126
+ useProgram = vi.fn((program: ProgramRecord | null) => this.calls.push(`useProgram:${program?.id ?? 'null'}`));
127
+ getUniformLocation = vi.fn((program: ProgramRecord, name: string) => {
128
+ this.calls.push(`getUniformLocation:${program.id}:${name}`);
129
+ return this.uniformLocations.get(name) ?? null;
130
+ });
131
+ getAttribLocation = vi.fn((program: ProgramRecord, name: string) => {
132
+ this.calls.push(`getAttribLocation:${program.id}:${name}`);
133
+ return this.attributeLocations.get(name) ?? -1;
134
+ });
135
+
136
+ createBuffer = vi.fn(() => ({ buffer: {} } as unknown as WebGLBuffer));
137
+ bindBuffer = vi.fn((target: GLenum, buffer: WebGLBuffer | null) => this.calls.push(`bindBuffer:${target}:${!!buffer}`));
138
+ bufferData = vi.fn((target: GLenum, data: number | BufferSource, usage: GLenum) =>
139
+ this.calls.push(`bufferData:${target}:${usage}:${typeof data === 'number' ? data : 'data'}`)
140
+ );
141
+ bufferSubData = vi.fn((target: GLenum, offset: number, data: BufferSource) =>
142
+ this.calls.push(`bufferSubData:${target}:${offset}:${data.byteLength ?? 'len'}`)
143
+ );
144
+ deleteBuffer = vi.fn((buffer: WebGLBuffer) => this.calls.push(`deleteBuffer:${!!buffer}`));
145
+
146
+ createVertexArray = vi.fn(() => ({ vao: {} } as unknown as WebGLVertexArrayObject));
147
+ bindVertexArray = vi.fn((vao: WebGLVertexArrayObject | null) => this.calls.push(`bindVertexArray:${!!vao}`));
148
+ enableVertexAttribArray = vi.fn((index: number) => this.calls.push(`enableAttrib:${index}`));
149
+ vertexAttribPointer = vi.fn(
150
+ (index: number, size: number, type: GLenum, normalized: boolean, stride: number, offset: number) =>
151
+ this.calls.push(`vertexAttribPointer:${index}:${size}:${type}:${normalized}:${stride}:${offset}`)
152
+ );
153
+ vertexAttribDivisor = vi.fn((index: number, divisor: number) => this.calls.push(`divisor:${index}:${divisor}`));
154
+ deleteVertexArray = vi.fn((vao: WebGLVertexArrayObject) => this.calls.push(`deleteVertexArray:${!!vao}`));
155
+
156
+ createTexture = vi.fn(() => ({ texture: {} } as unknown as WebGLTexture));
157
+ activeTexture = vi.fn((unit: GLenum) => this.calls.push(`activeTexture:${unit}`));
158
+ bindTexture = vi.fn((target: GLenum, texture: WebGLTexture | null) => this.calls.push(`bindTexture:${target}:${!!texture}`));
159
+ texParameteri = vi.fn((target: GLenum, pname: GLenum, param: GLint) =>
160
+ this.calls.push(`texParameteri:${target}:${pname}:${param}`)
161
+ );
162
+ texImage2D = vi.fn(
163
+ (
164
+ target: GLenum,
165
+ level: GLint,
166
+ internalFormat: GLenum,
167
+ width: GLsizei,
168
+ height: GLsizei,
169
+ border: GLint,
170
+ format: GLenum,
171
+ type: GLenum,
172
+ pixels: ArrayBufferView | null
173
+ ) =>
174
+ this.calls.push(
175
+ `texImage2D:${target}:${level}:${internalFormat}:${width}:${height}:${border}:${format}:${type}:${pixels ? 'data' : 'null'}`
176
+ )
177
+ );
178
+ texImage3D = vi.fn(); // Stub for compatibility
179
+ deleteTexture = vi.fn((texture: WebGLTexture) => this.calls.push(`deleteTexture:${!!texture}`));
180
+
181
+ createFramebuffer = vi.fn(() => ({ fb: {} } as unknown as WebGLFramebuffer));
182
+ bindFramebuffer = vi.fn((target: GLenum, framebuffer: WebGLFramebuffer | null) =>
183
+ this.calls.push(`bindFramebuffer:${target}:${!!framebuffer}`)
184
+ );
185
+ framebufferTexture2D = vi.fn(
186
+ (target: GLenum, attachment: GLenum, textarget: GLenum, texture: WebGLTexture | null, level: GLint) =>
187
+ this.calls.push(`framebufferTexture2D:${target}:${attachment}:${textarget}:${!!texture}:${level}`)
188
+ );
189
+ deleteFramebuffer = vi.fn((fb: WebGLFramebuffer) => this.calls.push(`deleteFramebuffer:${!!fb}`));
190
+ checkFramebufferStatus = vi.fn((target: GLenum) => this.FRAMEBUFFER_COMPLETE);
191
+
192
+ createRenderbuffer = vi.fn(() => ({ rb: {} } as unknown as WebGLRenderbuffer));
193
+ bindRenderbuffer = vi.fn((target: GLenum, renderbuffer: WebGLRenderbuffer | null) =>
194
+ this.calls.push(`bindRenderbuffer:${target}:${!!renderbuffer}`)
195
+ );
196
+ renderbufferStorage = vi.fn((target: GLenum, internalformat: GLenum, width: GLsizei, height: GLsizei) =>
197
+ this.calls.push(`renderbufferStorage:${target}:${internalformat}:${width}:${height}`)
198
+ );
199
+ framebufferRenderbuffer = vi.fn((target: GLenum, attachment: GLenum, renderbuffertarget: GLenum, renderbuffer: WebGLRenderbuffer | null) =>
200
+ this.calls.push(`framebufferRenderbuffer:${target}:${attachment}:${renderbuffertarget}:${!!renderbuffer}`)
201
+ );
202
+ deleteRenderbuffer = vi.fn((rb: WebGLRenderbuffer) => this.calls.push(`deleteRenderbuffer:${!!rb}`));
203
+
204
+ drawArrays = vi.fn((mode: GLenum, first: GLint, count: GLsizei) =>
205
+ this.calls.push(`drawArrays:${mode}:${first}:${count}`)
206
+ );
207
+
208
+ drawElements = vi.fn((mode: GLenum, count: GLsizei, type: GLenum, offset: GLintptr) =>
209
+ this.calls.push(`drawElements:${mode}:${count}:${type}:${offset}`)
210
+ );
211
+
212
+ // Queries
213
+ createQuery = vi.fn(() => ({}) as WebGLQuery);
214
+ beginQuery = vi.fn();
215
+ endQuery = vi.fn();
216
+ deleteQuery = vi.fn();
217
+ getQueryParameter = vi.fn();
218
+ getParameter = vi.fn();
219
+
220
+ uniform1f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat) =>
221
+ this.calls.push(`uniform1f:${location ? 'set' : 'null'}:${x}`)
222
+ );
223
+ uniform1i = vi.fn((location: WebGLUniformLocation | null, x: GLint) =>
224
+ this.calls.push(`uniform1i:${location ? 'set' : 'null'}:${x}`)
225
+ );
226
+ uniform4f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat, z: GLfloat, w: GLfloat) =>
227
+ this.calls.push(`uniform4f:${location ? 'set' : 'null'}:${x}:${y}:${z}:${w}`)
228
+ );
229
+ uniform3fv = vi.fn((location: WebGLUniformLocation | null, data: Float32List | number[]) =>
230
+ this.calls.push(`uniform3fv:${location ? 'set' : 'null'}:${Array.from(data as Iterable<number>).join(',')}`)
231
+ );
232
+ uniform3f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat, z: GLfloat) =>
233
+ this.calls.push(`uniform3f:${location ? 'set' : 'null'}:${x}:${y}:${z}`)
234
+ );
235
+ uniform2f = vi.fn((location: WebGLUniformLocation | null, x: GLfloat, y: GLfloat) =>
236
+ this.calls.push(`uniform2f:${location ? 'set' : 'null'}:${x}:${y}`)
237
+ );
238
+ uniform4fv = vi.fn((location: WebGLUniformLocation | null, data: Float32List) =>
239
+ this.calls.push(`uniform4fv:${location ? 'set' : 'null'}:${Array.from(data as Iterable<number>).join(',')}`)
240
+ );
241
+ uniformMatrix4fv = vi.fn(
242
+ (location: WebGLUniformLocation | null, transpose: GLboolean, data: Float32List | Iterable<number>) =>
243
+ this.calls.push(`uniformMatrix4fv:${location ? 'set' : 'null'}:${transpose}:${Array.from(data as Iterable<number>).join(',')}`)
244
+ );
245
+
246
+ uniformBlockBinding = vi.fn();
247
+
248
+ isContextLost = vi.fn(() => false);
249
+ }
250
+
251
+ export function createMockWebGL2Context(
252
+ overridesOrCanvas?: Partial<WebGL2RenderingContext> | HTMLCanvasElement
253
+ ): MockWebGL2RenderingContext {
254
+ let context: MockWebGL2RenderingContext;
255
+
256
+ if (overridesOrCanvas instanceof Object && 'width' in overridesOrCanvas && 'height' in overridesOrCanvas && 'getContext' in overridesOrCanvas) {
257
+ // It's likely a canvas element (or mock of it)
258
+ context = new MockWebGL2RenderingContext(overridesOrCanvas as HTMLCanvasElement);
259
+ } else {
260
+ context = new MockWebGL2RenderingContext();
261
+ if (overridesOrCanvas) {
262
+ Object.assign(context, overridesOrCanvas);
263
+ }
264
+ }
265
+
266
+ return context;
267
+ }
@@ -0,0 +1,262 @@
1
+ import { vi } from 'vitest';
2
+ import { globals } from 'webgpu';
3
+
4
+ export interface MockWebGPUContext {
5
+ adapter: GPUAdapter;
6
+ device: GPUDevice;
7
+ queue: GPUQueue;
8
+ }
9
+
10
+ /**
11
+ * Patches globalThis with WebGPU globals (GPUBufferUsage, etc.)
12
+ * and optionally patches navigator.gpu.
13
+ */
14
+ export function setupWebGPUMocks() {
15
+ // 1. Inject globals like GPUBufferUsage, GPUTextureUsage
16
+ Object.assign(globalThis, globals);
17
+
18
+ // 2. Setup Navigator mock
19
+ const mockGpu = {
20
+ requestAdapter: vi.fn(),
21
+ getPreferredCanvasFormat: vi.fn().mockReturnValue('bgra8unorm'),
22
+ };
23
+
24
+ // Create mocks for Adapter and Device
25
+ const mockAdapter = createMockGPUAdapter();
26
+ const mockDevice = createMockGPUDevice();
27
+
28
+ // Wire them up
29
+ mockGpu.requestAdapter.mockResolvedValue(mockAdapter);
30
+ // @ts-ignore - vitest mock manipulation
31
+ mockAdapter.requestDevice.mockResolvedValue(mockDevice);
32
+
33
+ if (!globalThis.navigator) {
34
+ // @ts-ignore
35
+ globalThis.navigator = {};
36
+ }
37
+
38
+ // Safely redefine navigator.gpu
39
+ try {
40
+ // If it exists and is configurable, define it.
41
+ // If it doesn't exist, define it.
42
+ // If it exists and is NOT configurable, we can't do much (but that shouldn't happen in our test env if we control it)
43
+ Object.defineProperty(globalThis.navigator, 'gpu', {
44
+ value: mockGpu,
45
+ writable: true,
46
+ configurable: true
47
+ });
48
+ } catch (e) {
49
+ // Fallback: simple assignment if defineProperty fails (e.g. some JSDOM quirks)
50
+ // @ts-ignore
51
+ globalThis.navigator.gpu = mockGpu;
52
+ }
53
+
54
+ return {
55
+ mockGpu,
56
+ mockAdapter,
57
+ mockDevice
58
+ };
59
+ }
60
+
61
+ export function createMockGPUAdapter(options: Partial<GPUAdapter> = {}): GPUAdapter {
62
+ return {
63
+ features: new Set(),
64
+ limits: {},
65
+ isFallbackAdapter: false,
66
+ requestDevice: vi.fn().mockResolvedValue(createMockGPUDevice()),
67
+ requestAdapterInfo: vi.fn().mockResolvedValue({}),
68
+ ...options,
69
+ } as unknown as GPUAdapter;
70
+ }
71
+
72
+ export function createMockGPUDevice(features: Set<GPUFeatureName> = new Set()): GPUDevice {
73
+ const queue = createMockQueue();
74
+
75
+ return {
76
+ features,
77
+ limits: {},
78
+ queue,
79
+ destroy: vi.fn(),
80
+ createBuffer: vi.fn((descriptor: GPUBufferDescriptor) => createMockGPUBuffer(descriptor)),
81
+ createTexture: vi.fn((descriptor: GPUTextureDescriptor) => createMockGPUTexture(descriptor)),
82
+ createSampler: vi.fn(() => createMockSampler()),
83
+ createBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
84
+ createPipelineLayout: vi.fn(() => ({ label: 'mock-pipeline-layout' })),
85
+ createBindGroup: vi.fn(() => ({ label: 'mock-bind-group' })),
86
+ createShaderModule: vi.fn((descriptor: GPUShaderModuleDescriptor) => createMockShaderModule(descriptor)),
87
+ createComputePipeline: vi.fn(() => createMockComputePipeline()),
88
+ createRenderPipeline: vi.fn(() => createMockRenderPipeline()),
89
+ createComputePipelineAsync: vi.fn().mockResolvedValue(createMockComputePipeline()),
90
+ createRenderPipelineAsync: vi.fn().mockResolvedValue(createMockRenderPipeline()),
91
+ createCommandEncoder: vi.fn(() => createMockCommandEncoder()),
92
+ createQuerySet: vi.fn(() => ({ label: 'mock-query-set' })),
93
+ pushErrorScope: vi.fn(),
94
+ popErrorScope: vi.fn().mockResolvedValue(null),
95
+ addEventListener: vi.fn(),
96
+ removeEventListener: vi.fn(),
97
+ dispatchEvent: vi.fn(),
98
+ onuncapturederror: null,
99
+ label: '',
100
+ lost: Promise.resolve({ reason: 'destroyed', message: 'Device lost' }),
101
+ } as unknown as GPUDevice;
102
+ }
103
+
104
+ export function createMockQueue(): GPUQueue {
105
+ return {
106
+ submit: vi.fn(),
107
+ onSubmittedWorkDone: vi.fn().mockResolvedValue(undefined),
108
+ writeBuffer: vi.fn(),
109
+ writeTexture: vi.fn(),
110
+ copyExternalImageToTexture: vi.fn(),
111
+ label: '',
112
+ } as unknown as GPUQueue;
113
+ }
114
+
115
+ export function createMockGPUBuffer(descriptor: GPUBufferDescriptor): GPUBuffer {
116
+ return {
117
+ size: descriptor.size,
118
+ usage: descriptor.usage,
119
+ mapState: 'unmapped',
120
+ mapAsync: vi.fn().mockResolvedValue(undefined),
121
+ getMappedRange: vi.fn(() => new ArrayBuffer(descriptor.size)),
122
+ unmap: vi.fn(),
123
+ destroy: vi.fn(),
124
+ label: descriptor.label || '',
125
+ } as unknown as GPUBuffer;
126
+ }
127
+
128
+ export function createMockGPUTexture(descriptor: GPUTextureDescriptor): GPUTexture {
129
+ const size = descriptor.size;
130
+ let width = 0;
131
+ let height = 0;
132
+ let depthOrArrayLayers = 1;
133
+
134
+ if (Array.isArray(size) || size instanceof Float32Array || size instanceof Uint32Array) {
135
+ // Iterable<number>
136
+ const arr = Array.from(size as Iterable<number>);
137
+ width = arr[0] || 0;
138
+ height = arr[1] || 1;
139
+ depthOrArrayLayers = arr[2] || 1;
140
+ } else if (typeof size === 'object') {
141
+ // GPUExtent3DDict
142
+ const dict = size as GPUExtent3DDict;
143
+ width = dict.width;
144
+ height = dict.height || 1;
145
+ depthOrArrayLayers = dict.depthOrArrayLayers || 1;
146
+ }
147
+
148
+ return {
149
+ width,
150
+ height,
151
+ depthOrArrayLayers,
152
+ mipLevelCount: descriptor.mipLevelCount || 1,
153
+ sampleCount: descriptor.sampleCount || 1,
154
+ dimension: descriptor.dimension || '2d',
155
+ format: descriptor.format,
156
+ usage: descriptor.usage,
157
+ createView: vi.fn(() => createMockTextureView()),
158
+ destroy: vi.fn(),
159
+ label: descriptor.label || '',
160
+ } as unknown as GPUTexture;
161
+ }
162
+
163
+ export function createMockTextureView(): GPUTextureView {
164
+ return {
165
+ label: '',
166
+ } as unknown as GPUTextureView;
167
+ }
168
+
169
+ export function createMockSampler(): GPUSampler {
170
+ return {
171
+ label: '',
172
+ } as unknown as GPUSampler;
173
+ }
174
+
175
+ export function createMockShaderModule(descriptor: GPUShaderModuleDescriptor): GPUShaderModule {
176
+ return {
177
+ getCompilationInfo: vi.fn().mockResolvedValue({ messages: [] }),
178
+ label: descriptor.label || '',
179
+ } as unknown as GPUShaderModule;
180
+ }
181
+
182
+ export function createMockComputePipeline(): GPUComputePipeline {
183
+ return {
184
+ getBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
185
+ label: '',
186
+ } as unknown as GPUComputePipeline;
187
+ }
188
+
189
+ export function createMockRenderPipeline(): GPURenderPipeline {
190
+ return {
191
+ getBindGroupLayout: vi.fn(() => ({ label: 'mock-bind-group-layout' })),
192
+ label: '',
193
+ } as unknown as GPURenderPipeline;
194
+ }
195
+
196
+ export function createMockCommandEncoder(): GPUCommandEncoder {
197
+ return {
198
+ beginRenderPass: vi.fn(() => createMockRenderPassEncoder()),
199
+ beginComputePass: vi.fn(() => createMockComputePassEncoder()),
200
+ copyBufferToBuffer: vi.fn(),
201
+ copyBufferToTexture: vi.fn(),
202
+ copyTextureToBuffer: vi.fn(),
203
+ copyTextureToTexture: vi.fn(),
204
+ clearBuffer: vi.fn(),
205
+ writeTimestamp: vi.fn(),
206
+ resolveQuerySet: vi.fn(),
207
+ finish: vi.fn(() => ({ label: 'mock-command-buffer' })),
208
+ pushDebugGroup: vi.fn(),
209
+ popDebugGroup: vi.fn(),
210
+ insertDebugMarker: vi.fn(),
211
+ label: '',
212
+ } as unknown as GPUCommandEncoder;
213
+ }
214
+
215
+ export function createMockRenderPassEncoder(): GPURenderPassEncoder {
216
+ return {
217
+ setPipeline: vi.fn(),
218
+ setIndexBuffer: vi.fn(),
219
+ setVertexBuffer: vi.fn(),
220
+ setBindGroup: vi.fn(),
221
+ setViewport: vi.fn(),
222
+ setScissorRect: vi.fn(),
223
+ setBlendConstant: vi.fn(),
224
+ setStencilReference: vi.fn(),
225
+ beginOcclusionQuery: vi.fn(),
226
+ endOcclusionQuery: vi.fn(),
227
+ executeBundles: vi.fn(),
228
+ draw: vi.fn(),
229
+ drawIndexed: vi.fn(),
230
+ drawIndirect: vi.fn(),
231
+ drawIndexedIndirect: vi.fn(),
232
+ end: vi.fn(),
233
+ pushDebugGroup: vi.fn(),
234
+ popDebugGroup: vi.fn(),
235
+ insertDebugMarker: vi.fn(),
236
+ label: '',
237
+ } as unknown as GPURenderPassEncoder;
238
+ }
239
+
240
+ export function createMockComputePassEncoder(): GPUComputePassEncoder {
241
+ return {
242
+ setPipeline: vi.fn(),
243
+ setBindGroup: vi.fn(),
244
+ dispatchWorkgroups: vi.fn(),
245
+ dispatchWorkgroupsIndirect: vi.fn(),
246
+ end: vi.fn(),
247
+ pushDebugGroup: vi.fn(),
248
+ popDebugGroup: vi.fn(),
249
+ insertDebugMarker: vi.fn(),
250
+ label: '',
251
+ } as unknown as GPUComputePassEncoder;
252
+ }
253
+
254
+ export function createMockWebGPUContext(): MockWebGPUContext {
255
+ const adapter = createMockGPUAdapter();
256
+ const device = createMockGPUDevice();
257
+ return {
258
+ adapter,
259
+ device,
260
+ queue: device.queue,
261
+ };
262
+ }
@@ -0,0 +1,103 @@
1
+ import { vi } from 'vitest';
2
+ import { createMockWebGL2Context, MockWebGL2RenderingContext } from './mocks/webgl.js';
3
+
4
+ // Define minimal mock interfaces for internal engine components not publicly exported.
5
+ interface MockPipeline {
6
+ render: ReturnType<typeof vi.fn>;
7
+ init: ReturnType<typeof vi.fn>;
8
+ resize: ReturnType<typeof vi.fn>;
9
+ }
10
+
11
+ interface MockCamera {
12
+ update: ReturnType<typeof vi.fn>;
13
+ getViewMatrix: ReturnType<typeof vi.fn>;
14
+ getProjectionMatrix: ReturnType<typeof vi.fn>;
15
+ getViewProjectionMatrix: ReturnType<typeof vi.fn>;
16
+ getPosition: ReturnType<typeof vi.fn>;
17
+ getForward: ReturnType<typeof vi.fn>;
18
+ getRight: ReturnType<typeof vi.fn>;
19
+ getUp: ReturnType<typeof vi.fn>;
20
+ extractFrustumPlanes: ReturnType<typeof vi.fn>;
21
+ transform: {
22
+ origin: number[];
23
+ angles: number[];
24
+ fov: number;
25
+ };
26
+ }
27
+
28
+ export interface MockRenderingContext {
29
+ gl: MockWebGL2RenderingContext;
30
+ camera: MockCamera;
31
+ pipelines: {
32
+ md2: MockPipeline;
33
+ bsp: MockPipeline;
34
+ sprite: MockPipeline;
35
+ poly: MockPipeline;
36
+ particle: MockPipeline;
37
+ };
38
+ }
39
+
40
+ export function createMockRenderingContext(): MockRenderingContext {
41
+ const gl = createMockWebGL2Context();
42
+
43
+ // Mock Camera
44
+ const camera: MockCamera = {
45
+ update: vi.fn(),
46
+ getViewMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
47
+ getProjectionMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
48
+ getViewProjectionMatrix: vi.fn().mockReturnValue(new Float32Array(16)),
49
+ getPosition: vi.fn().mockReturnValue([0, 0, 0]),
50
+ getForward: vi.fn().mockReturnValue([0, 0, -1]),
51
+ getRight: vi.fn().mockReturnValue([1, 0, 0]),
52
+ getUp: vi.fn().mockReturnValue([0, 1, 0]),
53
+ extractFrustumPlanes: vi.fn(),
54
+ transform: {
55
+ origin: [0, 0, 0],
56
+ angles: [0, 0, 0],
57
+ fov: 90
58
+ }
59
+ };
60
+
61
+ // Mock Pipelines
62
+ const md2: MockPipeline = {
63
+ render: vi.fn(),
64
+ init: vi.fn(),
65
+ resize: vi.fn(),
66
+ };
67
+
68
+ const bsp: MockPipeline = {
69
+ render: vi.fn(),
70
+ init: vi.fn(),
71
+ resize: vi.fn(),
72
+ };
73
+
74
+ const sprite: MockPipeline = {
75
+ render: vi.fn(),
76
+ init: vi.fn(),
77
+ resize: vi.fn(),
78
+ };
79
+
80
+ const poly: MockPipeline = {
81
+ render: vi.fn(),
82
+ init: vi.fn(),
83
+ resize: vi.fn(),
84
+ };
85
+
86
+ const particle: MockPipeline = {
87
+ render: vi.fn(),
88
+ init: vi.fn(),
89
+ resize: vi.fn(),
90
+ };
91
+
92
+ return {
93
+ gl,
94
+ camera,
95
+ pipelines: {
96
+ md2,
97
+ bsp,
98
+ sprite,
99
+ poly,
100
+ particle
101
+ }
102
+ };
103
+ }