@quake2ts/test-utils 0.0.838 → 0.0.840

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/dist/index.cjs CHANGED
@@ -55,6 +55,13 @@ __export(index_exports, {
55
55
  ShaderProgram: () => import_engine5.ShaderProgram,
56
56
  VertexBuffer: () => import_engine4.VertexBuffer,
57
57
  ZERO_VEC3: () => import_shared2.ZERO_VEC3,
58
+ buildMd2: () => buildMd2,
59
+ buildMd3: () => buildMd3,
60
+ buildPak: () => buildPak,
61
+ buildPcx: () => buildPcx,
62
+ buildTestBsp: () => buildTestBsp,
63
+ buildWal: () => buildWal,
64
+ buildWav: () => buildWav,
58
65
  captureAudioEvents: () => captureAudioEvents,
59
66
  captureCanvasDrawCalls: () => captureCanvasDrawCalls,
60
67
  captureFramebufferAsPNG: () => captureFramebufferAsPNG,
@@ -238,6 +245,7 @@ __export(index_exports, {
238
245
  createVisualTestScenario: () => createVisualTestScenario,
239
246
  createWebGLPlaywrightSetup: () => createWebGLPlaywrightSetup,
240
247
  createWebGLRenderTestSetup: () => createWebGLRenderTestSetup,
248
+ encodedVisForClusters: () => encodedVisForClusters,
241
249
  expectAnimationSnapshot: () => expectAnimationSnapshot,
242
250
  expectNoDoubleTransform: () => expectNoDoubleTransform,
243
251
  expectRendererCalls: () => expectRendererCalls,
@@ -268,6 +276,7 @@ __export(index_exports, {
268
276
  renderAndExpectSnapshot: () => renderAndExpectSnapshot,
269
277
  restoreSaveGameSnapshot: () => restoreSaveGameSnapshot,
270
278
  runComputeAndReadback: () => runComputeAndReadback,
279
+ runLengthVisRow: () => runLengthVisRow,
271
280
  savePNG: () => savePNG,
272
281
  serializeUserInfo: () => serializeUserInfo,
273
282
  setupBrowserEnvironment: () => setupBrowserEnvironment,
@@ -304,6 +313,7 @@ __export(index_exports, {
304
313
  testPipelineRendering: () => testPipelineRendering,
305
314
  testWebGLAnimation: () => testWebGLAnimation,
306
315
  testWebGLRenderer: () => testWebGLRenderer,
316
+ textData: () => textData,
307
317
  throttleBandwidth: () => throttleBandwidth,
308
318
  verifySmoothing: () => verifySmoothing,
309
319
  verifySnapshotConsistency: () => verifySnapshotConsistency,
@@ -5186,9 +5196,605 @@ function expectNoDoubleTransform(renderer) {
5186
5196
  (0, import_vitest19.expect)(warnings).toHaveLength(0);
5187
5197
  }
5188
5198
 
5199
+ // src/engine/builders/bspBuilder.ts
5200
+ var import_engine9 = require("@quake2ts/engine");
5201
+ var HEADER_LUMPS = 19;
5202
+ var HEADER_SIZE = 4 + 4 + HEADER_LUMPS * 8;
5203
+ function allocBuffer(size) {
5204
+ return new DataView(new ArrayBuffer(size));
5205
+ }
5206
+ function writeVec3(view, offset, vec) {
5207
+ view.setFloat32(offset, vec[0], true);
5208
+ view.setFloat32(offset + 4, vec[1], true);
5209
+ view.setFloat32(offset + 8, vec[2], true);
5210
+ }
5211
+ function encodePlanes(planes) {
5212
+ const view = allocBuffer(planes.length * 20);
5213
+ planes.forEach((plane, index) => {
5214
+ const base = index * 20;
5215
+ writeVec3(view, base, plane.normal);
5216
+ view.setFloat32(base + 12, plane.dist, true);
5217
+ view.setInt32(base + 16, plane.type, true);
5218
+ });
5219
+ return new Uint8Array(view.buffer);
5220
+ }
5221
+ function encodeVertices(vertices) {
5222
+ const view = allocBuffer(vertices.length * 12);
5223
+ vertices.forEach((vertex, index) => writeVec3(view, index * 12, vertex));
5224
+ return new Uint8Array(view.buffer);
5225
+ }
5226
+ function encodeNodes(nodes) {
5227
+ const view = allocBuffer(nodes.length * 28);
5228
+ nodes.forEach((node, index) => {
5229
+ const base = index * 28;
5230
+ view.setInt32(base, node.planeIndex, true);
5231
+ view.setInt32(base + 4, node.children[0], true);
5232
+ view.setInt32(base + 8, node.children[1], true);
5233
+ view.setInt16(base + 12, node.mins[0], true);
5234
+ view.setInt16(base + 14, node.mins[1], true);
5235
+ view.setInt16(base + 16, node.mins[2], true);
5236
+ view.setInt16(base + 18, node.maxs[0], true);
5237
+ view.setInt16(base + 20, node.maxs[1], true);
5238
+ view.setInt16(base + 22, node.maxs[2], true);
5239
+ view.setUint16(base + 24, node.firstFace, true);
5240
+ view.setUint16(base + 26, node.numFaces, true);
5241
+ });
5242
+ return new Uint8Array(view.buffer);
5243
+ }
5244
+ function encodeTexInfo(texInfos) {
5245
+ const view = allocBuffer(texInfos.length * 76);
5246
+ texInfos.forEach((tex, index) => {
5247
+ const base = index * 76;
5248
+ writeVec3(view, base, tex.s);
5249
+ view.setFloat32(base + 12, tex.sOffset, true);
5250
+ writeVec3(view, base + 16, tex.t);
5251
+ view.setFloat32(base + 28, tex.tOffset, true);
5252
+ view.setInt32(base + 32, tex.flags, true);
5253
+ view.setInt32(base + 36, tex.value, true);
5254
+ const textureBytes = new TextEncoder().encode(tex.texture);
5255
+ new Uint8Array(view.buffer).set(textureBytes.slice(0, 32), base + 40);
5256
+ view.setInt32(base + 72, tex.nextTexInfo, true);
5257
+ });
5258
+ return new Uint8Array(view.buffer);
5259
+ }
5260
+ function encodeFaces(faces) {
5261
+ const view = allocBuffer(faces.length * 20);
5262
+ faces.forEach((face, index) => {
5263
+ const base = index * 20;
5264
+ view.setUint16(base, face.planeIndex, true);
5265
+ view.setInt16(base + 2, face.side, true);
5266
+ view.setInt32(base + 4, face.firstEdge, true);
5267
+ view.setInt16(base + 8, face.numEdges, true);
5268
+ view.setInt16(base + 10, face.texInfo, true);
5269
+ face.styles.forEach((style, sIndex) => view.setUint8(base + 12 + sIndex, style));
5270
+ view.setInt32(base + 16, face.lightOffset, true);
5271
+ });
5272
+ return new Uint8Array(view.buffer);
5273
+ }
5274
+ function encodeLeafs(leafs) {
5275
+ const view = allocBuffer(leafs.length * 28);
5276
+ leafs.forEach((leaf, index) => {
5277
+ const base = index * 28;
5278
+ view.setInt32(base, leaf.contents, true);
5279
+ view.setInt16(base + 4, leaf.cluster, true);
5280
+ view.setInt16(base + 6, leaf.area, true);
5281
+ view.setInt16(base + 8, leaf.mins[0], true);
5282
+ view.setInt16(base + 10, leaf.mins[1], true);
5283
+ view.setInt16(base + 12, leaf.mins[2], true);
5284
+ view.setInt16(base + 14, leaf.maxs[0], true);
5285
+ view.setInt16(base + 16, leaf.maxs[1], true);
5286
+ view.setInt16(base + 18, leaf.maxs[2], true);
5287
+ view.setUint16(base + 20, leaf.firstLeafFace, true);
5288
+ view.setUint16(base + 22, leaf.numLeafFaces, true);
5289
+ view.setUint16(base + 24, leaf.firstLeafBrush, true);
5290
+ view.setUint16(base + 26, leaf.numLeafBrushes, true);
5291
+ });
5292
+ return new Uint8Array(view.buffer);
5293
+ }
5294
+ function encodeEdges(edges) {
5295
+ const view = allocBuffer(edges.length * 4);
5296
+ edges.forEach((edge, index) => {
5297
+ const base = index * 4;
5298
+ view.setUint16(base, edge[0], true);
5299
+ view.setUint16(base + 2, edge[1], true);
5300
+ });
5301
+ return new Uint8Array(view.buffer);
5302
+ }
5303
+ function encodeModels(models) {
5304
+ const entrySize = 48;
5305
+ const view = allocBuffer(models.length * entrySize);
5306
+ models.forEach((model, index) => {
5307
+ const base = index * entrySize;
5308
+ writeVec3(view, base, model.mins);
5309
+ writeVec3(view, base + 12, model.maxs);
5310
+ writeVec3(view, base + 24, model.origin);
5311
+ view.setInt32(base + 36, model.headNode, true);
5312
+ view.setInt32(base + 40, model.firstFace, true);
5313
+ view.setInt32(base + 44, model.numFaces, true);
5314
+ });
5315
+ return new Uint8Array(view.buffer);
5316
+ }
5317
+ function encodeBrushes(brushes) {
5318
+ const view = allocBuffer(brushes.length * 12);
5319
+ brushes.forEach((brush, index) => {
5320
+ const base = index * 12;
5321
+ view.setInt32(base, brush.firstSide, true);
5322
+ view.setInt32(base + 4, brush.numSides, true);
5323
+ view.setInt32(base + 8, brush.contents, true);
5324
+ });
5325
+ return new Uint8Array(view.buffer);
5326
+ }
5327
+ function encodeBrushSides(sides) {
5328
+ const view = allocBuffer(sides.length * 4);
5329
+ sides.forEach((side, index) => {
5330
+ const base = index * 4;
5331
+ view.setUint16(base, side.planeIndex, true);
5332
+ view.setInt16(base + 2, side.texInfo, true);
5333
+ });
5334
+ return new Uint8Array(view.buffer);
5335
+ }
5336
+ function encodeVisibility(numClusters, pvsRows, phsRows) {
5337
+ const headerBytes = 4 + numClusters * 8;
5338
+ const header = allocBuffer(headerBytes);
5339
+ header.setInt32(0, numClusters, true);
5340
+ let cursor = headerBytes;
5341
+ const payloads = [];
5342
+ for (let i = 0; i < numClusters; i += 1) {
5343
+ const pvs = pvsRows[i];
5344
+ const phs = phsRows?.[i] ?? pvs;
5345
+ const pvsOffset = cursor - 0;
5346
+ const phsOffset = cursor + pvs.byteLength - 0;
5347
+ payloads.push(pvs, phs);
5348
+ header.setInt32(4 + i * 8, pvsOffset, true);
5349
+ header.setInt32(8 + i * 8, phsOffset, true);
5350
+ cursor += pvs.byteLength + phs.byteLength;
5351
+ }
5352
+ const result = new Uint8Array(cursor);
5353
+ result.set(new Uint8Array(header.buffer), 0);
5354
+ let payloadCursor = headerBytes;
5355
+ for (const payload of payloads) {
5356
+ result.set(payload, payloadCursor);
5357
+ payloadCursor += payload.byteLength;
5358
+ }
5359
+ return result;
5360
+ }
5361
+ function buildTestBsp(options) {
5362
+ const lumps = {};
5363
+ lumps[import_engine9.BspLump.Entities] = new TextEncoder().encode(options.entities ?? '{"classname" "worldspawn"}\n');
5364
+ lumps[import_engine9.BspLump.Planes] = options.planes ? encodePlanes(options.planes) : new Uint8Array();
5365
+ lumps[import_engine9.BspLump.Vertices] = options.vertices ? encodeVertices(options.vertices) : new Uint8Array();
5366
+ lumps[import_engine9.BspLump.Visibility] = options.visibility ?? new Uint8Array();
5367
+ lumps[import_engine9.BspLump.Nodes] = options.nodes ? encodeNodes(options.nodes) : new Uint8Array();
5368
+ lumps[import_engine9.BspLump.TexInfo] = options.texInfo ? encodeTexInfo(options.texInfo) : new Uint8Array();
5369
+ lumps[import_engine9.BspLump.Faces] = options.faces ? encodeFaces(options.faces) : new Uint8Array();
5370
+ lumps[import_engine9.BspLump.Lighting] = options.lighting ?? new Uint8Array();
5371
+ lumps[import_engine9.BspLump.Leafs] = options.leafs ? encodeLeafs(options.leafs) : new Uint8Array();
5372
+ lumps[import_engine9.BspLump.LeafFaces] = options.leafFaces ? new Uint8Array(options.leafFaces.buffer) : new Uint8Array();
5373
+ lumps[import_engine9.BspLump.LeafBrushes] = options.leafBrushes ? new Uint8Array(options.leafBrushes.buffer) : new Uint8Array();
5374
+ lumps[import_engine9.BspLump.Edges] = options.edges ? encodeEdges(options.edges) : new Uint8Array();
5375
+ lumps[import_engine9.BspLump.SurfEdges] = options.surfEdges ? new Uint8Array(options.surfEdges.buffer) : new Uint8Array();
5376
+ lumps[import_engine9.BspLump.Models] = options.models ? encodeModels(options.models) : new Uint8Array();
5377
+ lumps[import_engine9.BspLump.Brushes] = options.brushes ? encodeBrushes(options.brushes) : new Uint8Array();
5378
+ lumps[import_engine9.BspLump.BrushSides] = options.brushSides ? encodeBrushSides(options.brushSides) : new Uint8Array();
5379
+ lumps[import_engine9.BspLump.Areas] = options.areas ?? new Uint8Array();
5380
+ lumps[import_engine9.BspLump.AreaPortals] = options.areaPortals ?? new Uint8Array();
5381
+ let cursor = HEADER_SIZE;
5382
+ const ordered = [];
5383
+ for (let i = 0; i < HEADER_LUMPS; i += 1) {
5384
+ const data = lumps[i] ?? new Uint8Array();
5385
+ const info = { offset: cursor, length: data.byteLength };
5386
+ ordered.push({ info, data });
5387
+ cursor += data.byteLength;
5388
+ }
5389
+ const buffer = new ArrayBuffer(cursor);
5390
+ const header = new DataView(buffer);
5391
+ header.setUint8(0, 73);
5392
+ header.setUint8(1, 66);
5393
+ header.setUint8(2, 83);
5394
+ header.setUint8(3, 80);
5395
+ header.setInt32(4, 38, true);
5396
+ ordered.forEach((entry, index) => {
5397
+ header.setInt32(8 + index * 8, entry.info.offset, true);
5398
+ header.setInt32(12 + index * 8, entry.info.length, true);
5399
+ });
5400
+ const body = new Uint8Array(buffer);
5401
+ ordered.forEach((entry) => body.set(entry.data, entry.info.offset));
5402
+ return buffer;
5403
+ }
5404
+ function runLengthVisRow(values) {
5405
+ return new Uint8Array(values);
5406
+ }
5407
+ function encodedVisForClusters(numClusters, rows) {
5408
+ const rowBytes = Math.ceil(numClusters / 8);
5409
+ const encodedRows = rows.map((row) => new Uint8Array(row));
5410
+ return encodeVisibility(numClusters, encodedRows);
5411
+ }
5412
+
5413
+ // src/engine/builders/md2Builder.ts
5414
+ function allocBuffer2(size) {
5415
+ return new DataView(new ArrayBuffer(size));
5416
+ }
5417
+ function writeCString(view, offset, text, max) {
5418
+ const bytes = new TextEncoder().encode(text);
5419
+ const length = Math.min(bytes.length, max - 1);
5420
+ new Uint8Array(view.buffer, view.byteOffset + offset, length).set(bytes.slice(0, length));
5421
+ view.setUint8(offset + length, 0);
5422
+ }
5423
+ function encodeFrames(frames, numVertices) {
5424
+ const frameSize = 40 + numVertices * 4;
5425
+ const view = allocBuffer2(frames.length * frameSize);
5426
+ frames.forEach((frame, frameIndex) => {
5427
+ const base = frameIndex * frameSize;
5428
+ const scale = frame.scale ?? { x: 1, y: 1, z: 1 };
5429
+ const translate = frame.translate ?? { x: 0, y: 0, z: 0 };
5430
+ view.setFloat32(base, scale.x, true);
5431
+ view.setFloat32(base + 4, scale.y, true);
5432
+ view.setFloat32(base + 8, scale.z, true);
5433
+ view.setFloat32(base + 12, translate.x, true);
5434
+ view.setFloat32(base + 16, translate.y, true);
5435
+ view.setFloat32(base + 20, translate.z, true);
5436
+ writeCString(view, base + 24, frame.name, 16);
5437
+ frame.vertices.forEach((vertex, index) => {
5438
+ const offset = base + 40 + index * 4;
5439
+ view.setUint8(offset, Math.round((vertex.position.x - translate.x) / scale.x));
5440
+ view.setUint8(offset + 1, Math.round((vertex.position.y - translate.y) / scale.y));
5441
+ view.setUint8(offset + 2, Math.round((vertex.position.z - translate.z) / scale.z));
5442
+ view.setUint8(offset + 3, vertex.normalIndex);
5443
+ });
5444
+ });
5445
+ return new Uint8Array(view.buffer);
5446
+ }
5447
+ function encodeGlCommands(commands) {
5448
+ const bytes = [];
5449
+ (commands ?? []).forEach((command) => {
5450
+ const count = command.vertices.length * (command.mode === "strip" ? 1 : -1);
5451
+ bytes.push(count);
5452
+ command.vertices.forEach((vertex) => {
5453
+ const floatView = new DataView(new ArrayBuffer(4));
5454
+ floatView.setFloat32(0, vertex.s, true);
5455
+ bytes.push(floatView.getInt32(0, true));
5456
+ floatView.setFloat32(0, vertex.t, true);
5457
+ bytes.push(floatView.getInt32(0, true));
5458
+ bytes.push(vertex.vertexIndex);
5459
+ });
5460
+ });
5461
+ bytes.push(0);
5462
+ const data = new Uint8Array(bytes.length * 4);
5463
+ const view = new DataView(data.buffer);
5464
+ bytes.forEach((value, index) => view.setInt32(index * 4, value, true));
5465
+ return { data, count: bytes.length };
5466
+ }
5467
+ function buildMd2(options) {
5468
+ const numVertices = options.frames[0]?.vertices.length ?? 0;
5469
+ const frameSize = 40 + numVertices * 4;
5470
+ const skins = options.skins ?? [];
5471
+ const { data: glData, count: glCount } = encodeGlCommands(options.glCommands);
5472
+ const headerSize = 68;
5473
+ const skinsSize = skins.length * 64;
5474
+ const texCoordSize = options.texCoords.length * 4;
5475
+ const triangleSize = options.triangles.length * 12;
5476
+ const frameBlockSize = options.frames.length * frameSize;
5477
+ const glSize = glData.length;
5478
+ const offsetSkins = headerSize;
5479
+ const offsetTexCoords = offsetSkins + skinsSize;
5480
+ const offsetTriangles = offsetTexCoords + texCoordSize;
5481
+ const offsetFrames = offsetTriangles + triangleSize;
5482
+ const offsetGlCommands = offsetFrames + frameBlockSize;
5483
+ const offsetEnd = offsetGlCommands + glSize;
5484
+ const view = allocBuffer2(offsetEnd);
5485
+ view.setInt32(0, 844121161, true);
5486
+ view.setInt32(4, 8, true);
5487
+ view.setInt32(8, options.skinWidth ?? 64, true);
5488
+ view.setInt32(12, options.skinHeight ?? 64, true);
5489
+ view.setInt32(16, frameSize, true);
5490
+ view.setInt32(20, skins.length, true);
5491
+ view.setInt32(24, numVertices, true);
5492
+ view.setInt32(28, options.texCoords.length, true);
5493
+ view.setInt32(32, options.triangles.length, true);
5494
+ view.setInt32(36, glCount, true);
5495
+ view.setInt32(40, options.frames.length, true);
5496
+ view.setInt32(44, offsetSkins, true);
5497
+ view.setInt32(48, offsetTexCoords, true);
5498
+ view.setInt32(52, offsetTriangles, true);
5499
+ view.setInt32(56, offsetFrames, true);
5500
+ view.setInt32(60, offsetGlCommands, true);
5501
+ view.setInt32(64, offsetEnd, true);
5502
+ skins.forEach((skin, index) => writeCString(view, offsetSkins + index * 64, skin, 64));
5503
+ options.texCoords.forEach((coord, index) => {
5504
+ const base = offsetTexCoords + index * 4;
5505
+ view.setInt16(base, coord.s, true);
5506
+ view.setInt16(base + 2, coord.t, true);
5507
+ });
5508
+ options.triangles.forEach((tri, index) => {
5509
+ const base = offsetTriangles + index * 12;
5510
+ view.setUint16(base, tri.vertexIndices[0], true);
5511
+ view.setUint16(base + 2, tri.vertexIndices[1], true);
5512
+ view.setUint16(base + 4, tri.vertexIndices[2], true);
5513
+ view.setUint16(base + 6, tri.texCoordIndices[0], true);
5514
+ view.setUint16(base + 8, tri.texCoordIndices[1], true);
5515
+ view.setUint16(base + 10, tri.texCoordIndices[2], true);
5516
+ });
5517
+ new Uint8Array(view.buffer, offsetFrames, frameBlockSize).set(encodeFrames(options.frames, numVertices));
5518
+ new Uint8Array(view.buffer, offsetGlCommands, glSize).set(glData);
5519
+ return view.buffer;
5520
+ }
5521
+
5522
+ // src/engine/builders/md3Builder.ts
5523
+ var HEADER_SIZE2 = 108;
5524
+ var FRAME_SIZE = 56;
5525
+ var TAG_SIZE = 112;
5526
+ var SURFACE_HEADER_SIZE = 108;
5527
+ function writeString(target, offset, text, length) {
5528
+ const encoder = new TextEncoder();
5529
+ const encoded = encoder.encode(text);
5530
+ const bytes = new Uint8Array(target.buffer, target.byteOffset + offset, length);
5531
+ bytes.fill(0);
5532
+ bytes.set(encoded.subarray(0, length));
5533
+ }
5534
+ function buildSurface(buffer, offset, surface, numFrames) {
5535
+ const view = new DataView(buffer);
5536
+ writeString(view, offset + 4, surface.name, 64);
5537
+ view.setInt32(offset, 860898377, true);
5538
+ view.setInt32(offset + 72, numFrames, true);
5539
+ view.setInt32(offset + 76, surface.shaders?.length ?? 0, true);
5540
+ view.setInt32(offset + 80, surface.vertices[0]?.length ?? 0, true);
5541
+ view.setInt32(offset + 84, surface.triangles.length, true);
5542
+ let cursor = SURFACE_HEADER_SIZE;
5543
+ view.setInt32(offset + 88, cursor, true);
5544
+ for (const tri of surface.triangles) {
5545
+ view.setInt32(offset + cursor, tri[0], true);
5546
+ view.setInt32(offset + cursor + 4, tri[1], true);
5547
+ view.setInt32(offset + cursor + 8, tri[2], true);
5548
+ cursor += 12;
5549
+ }
5550
+ view.setInt32(offset + 92, cursor, true);
5551
+ for (const shader of surface.shaders ?? []) {
5552
+ writeString(view, offset + cursor, shader.name, 64);
5553
+ view.setInt32(offset + cursor + 64, shader.index, true);
5554
+ cursor += 68;
5555
+ }
5556
+ view.setInt32(offset + 96, cursor, true);
5557
+ for (const tex of surface.texCoords) {
5558
+ view.setFloat32(offset + cursor, tex.s, true);
5559
+ view.setFloat32(offset + cursor + 4, tex.t, true);
5560
+ cursor += 8;
5561
+ }
5562
+ view.setInt32(offset + 100, cursor, true);
5563
+ for (const frame of surface.vertices) {
5564
+ for (const vertex of frame) {
5565
+ view.setInt16(offset + cursor, Math.round(vertex.position.x * 64), true);
5566
+ view.setInt16(offset + cursor + 2, Math.round(vertex.position.y * 64), true);
5567
+ view.setInt16(offset + cursor + 4, Math.round(vertex.position.z * 64), true);
5568
+ view.setUint16(offset + cursor + 6, vertex.latLng, true);
5569
+ cursor += 8;
5570
+ }
5571
+ }
5572
+ view.setInt32(offset + 104, cursor, true);
5573
+ return cursor;
5574
+ }
5575
+ function buildMd3(options) {
5576
+ const numFrames = options.frames.length;
5577
+ const numTags = options.tags?.length ?? 0;
5578
+ const numSurfaces = options.surfaces.length;
5579
+ let size = HEADER_SIZE2;
5580
+ size += numFrames * FRAME_SIZE;
5581
+ size += numFrames * numTags * TAG_SIZE;
5582
+ for (const surface of options.surfaces) {
5583
+ const verts = surface.vertices[0]?.length ?? 0;
5584
+ const triangles = surface.triangles.length;
5585
+ const shaders = surface.shaders?.length ?? 0;
5586
+ const texCoordBytes = surface.texCoords.length * 8;
5587
+ const surfaceSize = SURFACE_HEADER_SIZE + triangles * 12 + shaders * 68 + texCoordBytes + verts * 8 * numFrames;
5588
+ size += surfaceSize;
5589
+ }
5590
+ const buffer = new ArrayBuffer(size);
5591
+ const view = new DataView(buffer);
5592
+ writeString(view, 8, options.name ?? "builder", 64);
5593
+ view.setInt32(0, 860898377, true);
5594
+ view.setInt32(4, 15, true);
5595
+ view.setInt32(72, 0, true);
5596
+ view.setInt32(76, numFrames, true);
5597
+ view.setInt32(80, numTags, true);
5598
+ view.setInt32(84, numSurfaces, true);
5599
+ view.setInt32(88, 0, true);
5600
+ let offset = HEADER_SIZE2;
5601
+ view.setInt32(92, offset, true);
5602
+ for (const frame of options.frames) {
5603
+ view.setFloat32(offset, frame.min.x, true);
5604
+ view.setFloat32(offset + 4, frame.min.y, true);
5605
+ view.setFloat32(offset + 8, frame.min.z, true);
5606
+ view.setFloat32(offset + 12, frame.max.x, true);
5607
+ view.setFloat32(offset + 16, frame.max.y, true);
5608
+ view.setFloat32(offset + 20, frame.max.z, true);
5609
+ view.setFloat32(offset + 24, frame.origin.x, true);
5610
+ view.setFloat32(offset + 28, frame.origin.y, true);
5611
+ view.setFloat32(offset + 32, frame.origin.z, true);
5612
+ view.setFloat32(offset + 36, frame.radius, true);
5613
+ writeString(view, offset + 40, frame.name, 16);
5614
+ offset += FRAME_SIZE;
5615
+ }
5616
+ view.setInt32(96, offset, true);
5617
+ for (let frame = 0; frame < numFrames; frame += 1) {
5618
+ for (const tag of options.tags ?? []) {
5619
+ writeString(view, offset, tag.name, 64);
5620
+ view.setFloat32(offset + 64, tag.origin.x, true);
5621
+ view.setFloat32(offset + 68, tag.origin.y, true);
5622
+ view.setFloat32(offset + 72, tag.origin.z, true);
5623
+ for (let axis = 0; axis < 3; axis += 1) {
5624
+ const v = tag.axis[axis];
5625
+ view.setFloat32(offset + 76 + axis * 12, v.x, true);
5626
+ view.setFloat32(offset + 80 + axis * 12, v.y, true);
5627
+ view.setFloat32(offset + 84 + axis * 12, v.z, true);
5628
+ }
5629
+ offset += TAG_SIZE;
5630
+ }
5631
+ }
5632
+ view.setInt32(100, offset, true);
5633
+ for (let i = 0; i < options.surfaces.length; i += 1) {
5634
+ const written = buildSurface(buffer, offset, options.surfaces[i], numFrames);
5635
+ offset += written;
5636
+ }
5637
+ view.setInt32(104, offset, true);
5638
+ return buffer;
5639
+ }
5640
+
5641
+ // src/engine/builders/pakBuilder.ts
5642
+ var HEADER_SIZE3 = 12;
5643
+ var DIRECTORY_ENTRY_SIZE = 64;
5644
+ function buildPak(entries) {
5645
+ let offset = HEADER_SIZE3;
5646
+ const fileBuffers = [];
5647
+ const directory = new Uint8Array(entries.length * DIRECTORY_ENTRY_SIZE);
5648
+ const dirView = new DataView(directory.buffer);
5649
+ entries.forEach((entry, index) => {
5650
+ const data = entry.data;
5651
+ fileBuffers.push(data);
5652
+ const nameBytes = new TextEncoder().encode(entry.path);
5653
+ directory.set(nameBytes.slice(0, 56), index * DIRECTORY_ENTRY_SIZE);
5654
+ dirView.setInt32(index * DIRECTORY_ENTRY_SIZE + 56, offset, true);
5655
+ dirView.setInt32(index * DIRECTORY_ENTRY_SIZE + 60, data.byteLength, true);
5656
+ offset += data.byteLength;
5657
+ });
5658
+ const directoryOffset = offset;
5659
+ const directoryLength = directory.byteLength;
5660
+ const totalSize = HEADER_SIZE3 + fileBuffers.reduce((sum, buf) => sum + buf.byteLength, 0) + directoryLength;
5661
+ const buffer = new ArrayBuffer(totalSize);
5662
+ const view = new DataView(buffer);
5663
+ const writer = new Uint8Array(buffer);
5664
+ writer.set([80, 65, 67, 75]);
5665
+ view.setInt32(4, directoryOffset, true);
5666
+ view.setInt32(8, directoryLength, true);
5667
+ let cursor = HEADER_SIZE3;
5668
+ for (const data of fileBuffers) {
5669
+ writer.set(data, cursor);
5670
+ cursor += data.byteLength;
5671
+ }
5672
+ writer.set(directory, directoryOffset);
5673
+ return buffer;
5674
+ }
5675
+ function textData(text) {
5676
+ return new TextEncoder().encode(text);
5677
+ }
5678
+
5679
+ // src/engine/builders/pcxBuilder.ts
5680
+ function buildPcx(options) {
5681
+ const { width, height } = options;
5682
+ const headerSize = 128;
5683
+ const paletteSize = 769;
5684
+ const encodedPixels = [];
5685
+ for (const value of options.pixels) {
5686
+ if (value >= 192) {
5687
+ encodedPixels.push(193, value);
5688
+ } else {
5689
+ encodedPixels.push(value);
5690
+ }
5691
+ }
5692
+ const imageSize = encodedPixels.length;
5693
+ const buffer = new ArrayBuffer(headerSize + imageSize + paletteSize);
5694
+ const view = new DataView(buffer);
5695
+ view.setUint8(0, 10);
5696
+ view.setUint8(1, 5);
5697
+ view.setUint8(2, 1);
5698
+ view.setUint8(3, 8);
5699
+ view.setUint16(4, 0, true);
5700
+ view.setUint16(6, 0, true);
5701
+ view.setUint16(8, width - 1, true);
5702
+ view.setUint16(10, height - 1, true);
5703
+ view.setUint16(66, width, true);
5704
+ const encoded = new Uint8Array(buffer, headerSize, imageSize);
5705
+ encoded.set(encodedPixels);
5706
+ const paletteMarkerOffset = headerSize + imageSize;
5707
+ view.setUint8(paletteMarkerOffset, 12);
5708
+ const palette = new Uint8Array(buffer, paletteMarkerOffset + 1, 768);
5709
+ palette.fill(0);
5710
+ if (options.palette) {
5711
+ palette.set(options.palette.subarray(0, 768));
5712
+ } else {
5713
+ for (let i = 0; i < 256; i += 1) {
5714
+ palette[i * 3] = i;
5715
+ palette[i * 3 + 1] = 255 - i;
5716
+ palette[i * 3 + 2] = i;
5717
+ }
5718
+ }
5719
+ return buffer;
5720
+ }
5721
+
5722
+ // src/engine/builders/walBuilder.ts
5723
+ function buildWal(options) {
5724
+ const { width, height } = options;
5725
+ const headerSize = 100;
5726
+ const mipSizes = [
5727
+ width * height,
5728
+ Math.max(1, (width >> 1) * (height >> 1)),
5729
+ Math.max(1, (width >> 2) * (height >> 2)),
5730
+ Math.max(1, (width >> 3) * (height >> 3))
5731
+ ];
5732
+ const totalSize = headerSize + mipSizes.reduce((a, b) => a + b, 0);
5733
+ const buffer = new ArrayBuffer(totalSize);
5734
+ const view = new DataView(buffer);
5735
+ const encoder = new TextEncoder();
5736
+ new Uint8Array(buffer, 0, 32).set(encoder.encode(options.name));
5737
+ view.setInt32(32, width, true);
5738
+ view.setInt32(36, height, true);
5739
+ let offset = headerSize;
5740
+ mipSizes.forEach((size, index) => {
5741
+ view.setInt32(40 + index * 4, offset, true);
5742
+ const data = new Uint8Array(buffer, offset, size);
5743
+ for (let i = 0; i < size; i += 1) {
5744
+ data[i] = (i + index) % 256;
5745
+ }
5746
+ offset += size;
5747
+ });
5748
+ new Uint8Array(buffer, 56, 32).set(encoder.encode(options.name + "_anim"));
5749
+ view.setInt32(88, 0, true);
5750
+ view.setInt32(92, 0, true);
5751
+ view.setInt32(96, 0, true);
5752
+ return buffer;
5753
+ }
5754
+
5755
+ // src/engine/builders/wavBuilder.ts
5756
+ function buildWav(options) {
5757
+ const bitsPerSample = options.bitsPerSample ?? 16;
5758
+ const bytesPerSample = bitsPerSample / 8;
5759
+ const frameCount = options.samples.length / options.channels;
5760
+ const dataSize = frameCount * options.channels * bytesPerSample;
5761
+ const buffer = new ArrayBuffer(44 + dataSize);
5762
+ const view = new DataView(buffer);
5763
+ const writeString2 = (offset2, text) => {
5764
+ new Uint8Array(buffer, offset2, text.length).set(new TextEncoder().encode(text));
5765
+ };
5766
+ writeString2(0, "RIFF");
5767
+ view.setUint32(4, 36 + dataSize, true);
5768
+ writeString2(8, "WAVE");
5769
+ writeString2(12, "fmt ");
5770
+ view.setUint32(16, 16, true);
5771
+ view.setUint16(20, 1, true);
5772
+ view.setUint16(22, options.channels, true);
5773
+ view.setUint32(24, options.sampleRate, true);
5774
+ view.setUint32(28, options.sampleRate * options.channels * bytesPerSample, true);
5775
+ view.setUint16(32, options.channels * bytesPerSample, true);
5776
+ view.setUint16(34, bitsPerSample, true);
5777
+ writeString2(36, "data");
5778
+ view.setUint32(40, dataSize, true);
5779
+ let offset = 44;
5780
+ for (let i = 0; i < options.samples.length; i += 1) {
5781
+ const sample = options.samples[i];
5782
+ if (bitsPerSample === 8) {
5783
+ view.setUint8(offset, Math.max(0, Math.min(255, Math.round(sample * 128 + 128))));
5784
+ offset += 1;
5785
+ } else if (bitsPerSample === 16) {
5786
+ view.setInt16(offset, Math.round(sample * 32767), true);
5787
+ offset += 2;
5788
+ } else {
5789
+ throw new Error("Unsupported bit depth for builder");
5790
+ }
5791
+ }
5792
+ return buffer;
5793
+ }
5794
+
5189
5795
  // src/client/helpers/view.ts
5190
5796
  var import_gl_matrix = require("gl-matrix");
5191
- var import_engine9 = require("@quake2ts/engine");
5797
+ var import_engine10 = require("@quake2ts/engine");
5192
5798
  function toVec3(v) {
5193
5799
  if (v instanceof Float32Array && v.length === 3) {
5194
5800
  return v;
@@ -5202,7 +5808,7 @@ function toVec3(v) {
5202
5808
  return import_gl_matrix.vec3.create();
5203
5809
  }
5204
5810
  function createMockCamera(overrides = {}) {
5205
- const camera = new import_engine9.Camera();
5811
+ const camera = new import_engine10.Camera();
5206
5812
  if (overrides.position) {
5207
5813
  camera.position = toVec3(overrides.position);
5208
5814
  }
@@ -5362,7 +5968,7 @@ function createMockNotification(type, message, duration = 3e3) {
5362
5968
  }
5363
5969
 
5364
5970
  // src/client/mocks/network.ts
5365
- var import_engine10 = require("@quake2ts/engine");
5971
+ var import_engine11 = require("@quake2ts/engine");
5366
5972
  function createMockServerMessage(type, data = new Uint8Array()) {
5367
5973
  return { type, data };
5368
5974
  }
@@ -5374,7 +5980,7 @@ function createMockSnapshot(serverFrame, entities = [], playerState, deltaFrame
5374
5980
  areaBytes: 0,
5375
5981
  areaBits: new Uint8Array(),
5376
5982
  playerState: {
5377
- ...(0, import_engine10.createEmptyProtocolPlayerState)(),
5983
+ ...(0, import_engine11.createEmptyProtocolPlayerState)(),
5378
5984
  ...playerState
5379
5985
  },
5380
5986
  packetEntities: {
@@ -5391,7 +5997,7 @@ function createMockDeltaFrame(serverFrame, deltaFrame, entities = [], playerStat
5391
5997
  areaBytes: 0,
5392
5998
  areaBits: new Uint8Array(),
5393
5999
  playerState: {
5394
- ...(0, import_engine10.createEmptyProtocolPlayerState)(),
6000
+ ...(0, import_engine11.createEmptyProtocolPlayerState)(),
5395
6001
  ...playerState
5396
6002
  },
5397
6003
  packetEntities: {
@@ -5411,7 +6017,7 @@ function simulatePacketLoss(messages, lossPercent) {
5411
6017
  return messages.filter(() => Math.random() * 100 >= lossPercent);
5412
6018
  }
5413
6019
  function createMockEntityState(number, modelIndex = 0, origin = { x: 0, y: 0, z: 0 }, overrides) {
5414
- const state = (0, import_engine10.createEmptyEntityState)();
6020
+ const state = (0, import_engine11.createEmptyEntityState)();
5415
6021
  state.number = number;
5416
6022
  state.modelindex = modelIndex;
5417
6023
  state.origin.x = origin.x ?? 0;
@@ -5861,6 +6467,13 @@ function createVisualTestScenario(sceneName) {
5861
6467
  ShaderProgram,
5862
6468
  VertexBuffer,
5863
6469
  ZERO_VEC3,
6470
+ buildMd2,
6471
+ buildMd3,
6472
+ buildPak,
6473
+ buildPcx,
6474
+ buildTestBsp,
6475
+ buildWal,
6476
+ buildWav,
5864
6477
  captureAudioEvents,
5865
6478
  captureCanvasDrawCalls,
5866
6479
  captureFramebufferAsPNG,
@@ -6044,6 +6657,7 @@ function createVisualTestScenario(sceneName) {
6044
6657
  createVisualTestScenario,
6045
6658
  createWebGLPlaywrightSetup,
6046
6659
  createWebGLRenderTestSetup,
6660
+ encodedVisForClusters,
6047
6661
  expectAnimationSnapshot,
6048
6662
  expectNoDoubleTransform,
6049
6663
  expectRendererCalls,
@@ -6074,6 +6688,7 @@ function createVisualTestScenario(sceneName) {
6074
6688
  renderAndExpectSnapshot,
6075
6689
  restoreSaveGameSnapshot,
6076
6690
  runComputeAndReadback,
6691
+ runLengthVisRow,
6077
6692
  savePNG,
6078
6693
  serializeUserInfo,
6079
6694
  setupBrowserEnvironment,
@@ -6110,6 +6725,7 @@ function createVisualTestScenario(sceneName) {
6110
6725
  testPipelineRendering,
6111
6726
  testWebGLAnimation,
6112
6727
  testWebGLRenderer,
6728
+ textData,
6113
6729
  throttleBandwidth,
6114
6730
  verifySmoothing,
6115
6731
  verifySnapshotConsistency,