quake2ts 0.0.562 → 0.0.564

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.
@@ -603,6 +603,214 @@ function createMockServer(overrides) {
603
603
  };
604
604
  }
605
605
 
606
+ // src/server/mocks/connection.ts
607
+ import { ClientState as ClientState2 } from "@quake2ts/server";
608
+ var HandshakeStage = /* @__PURE__ */ ((HandshakeStage2) => {
609
+ HandshakeStage2[HandshakeStage2["None"] = 0] = "None";
610
+ HandshakeStage2[HandshakeStage2["Challenge"] = 1] = "Challenge";
611
+ HandshakeStage2[HandshakeStage2["Connect"] = 2] = "Connect";
612
+ HandshakeStage2[HandshakeStage2["Info"] = 3] = "Info";
613
+ HandshakeStage2[HandshakeStage2["Active"] = 4] = "Active";
614
+ return HandshakeStage2;
615
+ })(HandshakeStage || {});
616
+ function serializeUserInfo(info) {
617
+ return Object.entries(info).reduce((acc, [key, value]) => {
618
+ if (value !== void 0) {
619
+ return `${acc}\\${key}\\${value}`;
620
+ }
621
+ return acc;
622
+ }, "");
623
+ }
624
+ function createMockUserInfo(overrides) {
625
+ return {
626
+ name: "Player",
627
+ skin: "male/grunt",
628
+ model: "male/grunt",
629
+ fov: 90,
630
+ hand: 0,
631
+ rate: 25e3,
632
+ msg: 1,
633
+ ...overrides
634
+ };
635
+ }
636
+ function createMockConnection(state = ClientState2.Connected, overrides) {
637
+ return createMockServerClient(0, {
638
+ state,
639
+ userInfo: serializeUserInfo(createMockUserInfo()),
640
+ challenge: Math.floor(Math.random() * 1e5),
641
+ ...overrides
642
+ });
643
+ }
644
+ function createMockHandshake(stage = 0 /* None */) {
645
+ return {
646
+ stage,
647
+ clientNum: -1,
648
+ challenge: 0,
649
+ qport: Math.floor(Math.random() * 65536)
650
+ };
651
+ }
652
+ async function simulateHandshake(client, server) {
653
+ const challenge = Math.floor(Math.random() * 1e5);
654
+ client.challenge = challenge;
655
+ if (server && server.clients) {
656
+ }
657
+ client.state = ClientState2.Connected;
658
+ client.state = ClientState2.Active;
659
+ return true;
660
+ }
661
+
662
+ // src/server/helpers/multiplayer.ts
663
+ import { ClientState as ClientState3 } from "@quake2ts/server";
664
+ function createMultiplayerTestScenario(numPlayers = 2) {
665
+ const baseServer = createMockServerState();
666
+ const clients = [];
667
+ const entities = [];
668
+ const server = {
669
+ ...baseServer,
670
+ clients: new Array(numPlayers).fill(null),
671
+ entities: []
672
+ };
673
+ for (let i = 0; i < numPlayers; i++) {
674
+ const client = createMockServerClient(i, {
675
+ state: ClientState3.Active,
676
+ userInfo: serializeUserInfo(createMockUserInfo({ name: `Player${i}` }))
677
+ });
678
+ const entity = {
679
+ classname: "player",
680
+ s: { origin: { x: 0, y: 0, z: 0 }, number: i + 1 },
681
+ client
682
+ };
683
+ client.edict = entity;
684
+ server.clients[i] = client;
685
+ clients.push(client);
686
+ entities.push(entity);
687
+ }
688
+ server.entities = entities;
689
+ return {
690
+ server,
691
+ clients,
692
+ entities
693
+ };
694
+ }
695
+ async function simulatePlayerJoin(server, userInfo) {
696
+ const index = server.clients.findIndex((c) => !c || c.state === ClientState3.Free);
697
+ if (index === -1) {
698
+ throw new Error("Server full");
699
+ }
700
+ const client = createMockServerClient(index, {
701
+ state: ClientState3.Connected,
702
+ userInfo: serializeUserInfo(createMockUserInfo(userInfo))
703
+ });
704
+ server.clients[index] = client;
705
+ client.state = ClientState3.Active;
706
+ const entity = {
707
+ classname: "player",
708
+ s: { origin: { x: 0, y: 0, z: 0 }, number: index + 1 },
709
+ client
710
+ };
711
+ client.edict = entity;
712
+ if (server.entities && Array.isArray(server.entities)) {
713
+ server.entities[index + 1] = entity;
714
+ }
715
+ return client;
716
+ }
717
+ function simulatePlayerLeave(server, clientNum) {
718
+ const client = server.clients[clientNum];
719
+ if (client) {
720
+ client.state = ClientState3.Free;
721
+ client.edict = null;
722
+ }
723
+ }
724
+ function simulateServerTick(server, deltaTime = 0.1) {
725
+ server.time += deltaTime;
726
+ server.frame++;
727
+ server.clients.forEach((client) => {
728
+ if (client && client.state === ClientState3.Active) {
729
+ }
730
+ });
731
+ }
732
+ function simulatePlayerInput(client, input) {
733
+ const cmd = {
734
+ msec: 100,
735
+ buttons: 0,
736
+ angles: { x: 0, y: 0, z: 0 },
737
+ forwardmove: 0,
738
+ sidemove: 0,
739
+ upmove: 0,
740
+ sequence: client.lastCmd.sequence + 1,
741
+ lightlevel: 0,
742
+ impulse: 0,
743
+ ...input
744
+ };
745
+ client.lastCmd = cmd;
746
+ client.commandQueue.push(cmd);
747
+ client.commandCount++;
748
+ }
749
+
750
+ // src/server/helpers/snapshot.ts
751
+ function createServerSnapshot(serverState, clientNum) {
752
+ const visibleEntities = [];
753
+ if (serverState.baselines) {
754
+ serverState.baselines.forEach((ent, index) => {
755
+ if (ent && index !== clientNum + 1) {
756
+ visibleEntities.push({ ...ent });
757
+ }
758
+ });
759
+ }
760
+ return {
761
+ serverTime: serverState.time,
762
+ playerState: {
763
+ origin: { x: 0, y: 0, z: 0 },
764
+ viewangles: { x: 0, y: 0, z: 0 },
765
+ pm_type: 0
766
+ },
767
+ entities: visibleEntities
768
+ };
769
+ }
770
+ function createDeltaSnapshot(oldSnapshot, newSnapshot) {
771
+ const deltaEntities = [];
772
+ const removedEntities = [];
773
+ const oldMap = new Map(oldSnapshot.entities.map((e) => [e.number, e]));
774
+ const newMap = new Map(newSnapshot.entities.map((e) => [e.number, e]));
775
+ newSnapshot.entities.forEach((newEnt) => {
776
+ const oldEnt = oldMap.get(newEnt.number);
777
+ if (!oldEnt) {
778
+ deltaEntities.push(newEnt);
779
+ } else if (JSON.stringify(newEnt) !== JSON.stringify(oldEnt)) {
780
+ deltaEntities.push(newEnt);
781
+ }
782
+ });
783
+ oldSnapshot.entities.forEach((oldEnt) => {
784
+ if (!newMap.has(oldEnt.number)) {
785
+ removedEntities.push(oldEnt.number);
786
+ }
787
+ });
788
+ return {
789
+ snapshot: newSnapshot,
790
+ deltaEntities,
791
+ removedEntities
792
+ };
793
+ }
794
+ function verifySnapshotConsistency(snapshots) {
795
+ const report = { valid: true, errors: [] };
796
+ if (snapshots.length < 2) return report;
797
+ for (let i = 1; i < snapshots.length; i++) {
798
+ const prev = snapshots[i - 1];
799
+ const curr = snapshots[i];
800
+ if (curr.serverTime <= prev.serverTime) {
801
+ report.valid = false;
802
+ report.errors.push(`Snapshot ${i} has time ${curr.serverTime} <= prev ${prev.serverTime}`);
803
+ }
804
+ }
805
+ return report;
806
+ }
807
+ async function simulateSnapshotDelivery(snapshot, reliability = 1) {
808
+ if (Math.random() > reliability) {
809
+ return null;
810
+ }
811
+ return snapshot;
812
+ }
813
+
606
814
  // src/setup/browser.ts
607
815
  import { JSDOM } from "jsdom";
608
816
  import { Canvas, Image, ImageData } from "@napi-rs/canvas";
@@ -1080,8 +1288,8 @@ function createMockImage(width, height, src) {
1080
1288
  }
1081
1289
 
1082
1290
  // src/setup/node.ts
1083
- function setupNodeEnvironment() {
1084
- if (typeof global.fetch === "undefined") {
1291
+ function setupNodeEnvironment(options = {}) {
1292
+ if (options.polyfillFetch && typeof global.fetch === "undefined") {
1085
1293
  }
1086
1294
  }
1087
1295
 
@@ -1110,6 +1318,61 @@ function createMockIndexedDB() {
1110
1318
  return indexedDB;
1111
1319
  }
1112
1320
  function createStorageTestScenario(storageType = "local") {
1321
+ if (storageType === "indexed") {
1322
+ const dbName = `test-db-${Math.random().toString(36).substring(7)}`;
1323
+ const storeName = "test-store";
1324
+ const storage2 = createMockIndexedDB();
1325
+ return {
1326
+ storage: storage2,
1327
+ populate: async (data) => {
1328
+ return new Promise((resolve, reject) => {
1329
+ const req = storage2.open(dbName, 1);
1330
+ req.onupgradeneeded = (e) => {
1331
+ const db = e.target.result;
1332
+ db.createObjectStore(storeName);
1333
+ };
1334
+ req.onsuccess = (e) => {
1335
+ const db = e.target.result;
1336
+ const tx = db.transaction(storeName, "readwrite");
1337
+ const store = tx.objectStore(storeName);
1338
+ Object.entries(data).forEach(([k, v]) => store.put(v, k));
1339
+ tx.oncomplete = () => {
1340
+ db.close();
1341
+ resolve();
1342
+ };
1343
+ tx.onerror = () => reject(tx.error);
1344
+ };
1345
+ req.onerror = () => reject(req.error);
1346
+ });
1347
+ },
1348
+ verify: async (key, value) => {
1349
+ return new Promise((resolve, reject) => {
1350
+ const req = storage2.open(dbName, 1);
1351
+ req.onsuccess = (e) => {
1352
+ const db = e.target.result;
1353
+ if (!db.objectStoreNames.contains(storeName)) {
1354
+ db.close();
1355
+ resolve(false);
1356
+ return;
1357
+ }
1358
+ const tx = db.transaction(storeName, "readonly");
1359
+ const store = tx.objectStore(storeName);
1360
+ const getReq = store.get(key);
1361
+ getReq.onsuccess = () => {
1362
+ const result = getReq.result === value;
1363
+ db.close();
1364
+ resolve(result);
1365
+ };
1366
+ getReq.onerror = () => {
1367
+ db.close();
1368
+ resolve(false);
1369
+ };
1370
+ };
1371
+ req.onerror = () => reject(req.error);
1372
+ });
1373
+ }
1374
+ };
1375
+ }
1113
1376
  const storage = storageType === "local" ? createMockLocalStorage() : createMockSessionStorage();
1114
1377
  return {
1115
1378
  storage,
@@ -1124,7 +1387,7 @@ function createStorageTestScenario(storageType = "local") {
1124
1387
 
1125
1388
  // src/setup/audio.ts
1126
1389
  function createMockAudioContext() {
1127
- return {
1390
+ const context = {
1128
1391
  createGain: () => ({
1129
1392
  connect: () => {
1130
1393
  },
@@ -1173,8 +1436,23 @@ function createMockAudioContext() {
1173
1436
  sampleRate,
1174
1437
  numberOfChannels: channels,
1175
1438
  getChannelData: () => new Float32Array(length)
1176
- })
1439
+ }),
1440
+ // Helper to track events if needed
1441
+ _events: []
1177
1442
  };
1443
+ return new Proxy(context, {
1444
+ get(target, prop, receiver) {
1445
+ if (prop === "_events") return target._events;
1446
+ const value = Reflect.get(target, prop, receiver);
1447
+ if (typeof value === "function") {
1448
+ return (...args) => {
1449
+ target._events.push({ type: String(prop), args });
1450
+ return Reflect.apply(value, target, args);
1451
+ };
1452
+ }
1453
+ return value;
1454
+ }
1455
+ });
1178
1456
  }
1179
1457
  function setupMockAudioContext() {
1180
1458
  if (typeof global.AudioContext === "undefined" && typeof global.window !== "undefined") {
@@ -1195,7 +1473,7 @@ function teardownMockAudioContext() {
1195
1473
  }
1196
1474
  }
1197
1475
  function captureAudioEvents(context) {
1198
- return [];
1476
+ return context._events || [];
1199
1477
  }
1200
1478
 
1201
1479
  // src/setup/timing.ts
@@ -1487,25 +1765,142 @@ async function captureGameState(page) {
1487
1765
  return {};
1488
1766
  });
1489
1767
  }
1768
+
1769
+ // src/e2e/network.ts
1770
+ var CONDITIONS = {
1771
+ "good": {
1772
+ offline: false,
1773
+ downloadThroughput: 10 * 1024 * 1024,
1774
+ // 10 Mbps
1775
+ uploadThroughput: 5 * 1024 * 1024,
1776
+ // 5 Mbps
1777
+ latency: 20
1778
+ },
1779
+ "slow": {
1780
+ offline: false,
1781
+ downloadThroughput: 500 * 1024,
1782
+ // 500 Kbps
1783
+ uploadThroughput: 500 * 1024,
1784
+ latency: 400
1785
+ },
1786
+ "unstable": {
1787
+ offline: false,
1788
+ downloadThroughput: 1 * 1024 * 1024,
1789
+ uploadThroughput: 1 * 1024 * 1024,
1790
+ latency: 100
1791
+ },
1792
+ "offline": {
1793
+ offline: true,
1794
+ downloadThroughput: 0,
1795
+ uploadThroughput: 0,
1796
+ latency: 0
1797
+ }
1798
+ };
1799
+ function simulateNetworkCondition(condition) {
1800
+ const config = CONDITIONS[condition];
1801
+ return createCustomNetworkCondition(config.latency, 0, 0, config);
1802
+ }
1803
+ function createCustomNetworkCondition(latency, jitter = 0, packetLoss = 0, baseConfig) {
1804
+ return {
1805
+ async apply(page) {
1806
+ const client = await page.context().newCDPSession(page);
1807
+ await client.send("Network.enable");
1808
+ await client.send("Network.emulateNetworkConditions", {
1809
+ offline: baseConfig?.offline || false,
1810
+ latency: latency + Math.random() * jitter,
1811
+ downloadThroughput: baseConfig?.downloadThroughput || -1,
1812
+ uploadThroughput: baseConfig?.uploadThroughput || -1
1813
+ });
1814
+ },
1815
+ async clear(page) {
1816
+ const client = await page.context().newCDPSession(page);
1817
+ await client.send("Network.emulateNetworkConditions", {
1818
+ offline: false,
1819
+ latency: 0,
1820
+ downloadThroughput: -1,
1821
+ uploadThroughput: -1
1822
+ });
1823
+ }
1824
+ };
1825
+ }
1826
+ async function throttleBandwidth(page, bytesPerSecond) {
1827
+ const simulator = createCustomNetworkCondition(0, 0, 0, {
1828
+ offline: false,
1829
+ latency: 0,
1830
+ downloadThroughput: bytesPerSecond,
1831
+ uploadThroughput: bytesPerSecond
1832
+ });
1833
+ await simulator.apply(page);
1834
+ }
1835
+
1836
+ // src/e2e/visual.ts
1837
+ import path from "path";
1838
+ import fs from "fs/promises";
1839
+ async function captureGameScreenshot(page, name, options = {}) {
1840
+ const dir = options.dir || "__screenshots__";
1841
+ const screenshotPath = path.join(dir, `${name}.png`);
1842
+ await fs.mkdir(dir, { recursive: true });
1843
+ return await page.screenshot({
1844
+ path: screenshotPath,
1845
+ fullPage: options.fullPage ?? false,
1846
+ animations: "disabled",
1847
+ caret: "hide"
1848
+ });
1849
+ }
1850
+ async function compareScreenshots(baseline, current, threshold = 0.1) {
1851
+ if (baseline.equals(current)) {
1852
+ return { pixelDiff: 0, matched: true };
1853
+ }
1854
+ return {
1855
+ pixelDiff: -1,
1856
+ // Unknown magnitude
1857
+ matched: false
1858
+ };
1859
+ }
1860
+ function createVisualTestScenario(page, sceneName) {
1861
+ return {
1862
+ async capture(snapshotName) {
1863
+ return await captureGameScreenshot(page, `${sceneName}-${snapshotName}`);
1864
+ },
1865
+ async compare(snapshotName, baselineDir) {
1866
+ const name = `${sceneName}-${snapshotName}`;
1867
+ const current = await captureGameScreenshot(page, name, { dir: "__screenshots__/current" });
1868
+ try {
1869
+ const baselinePath = path.join(baselineDir, `${name}.png`);
1870
+ const baseline = await fs.readFile(baselinePath);
1871
+ return await compareScreenshots(baseline, current);
1872
+ } catch (e) {
1873
+ return { pixelDiff: -1, matched: false };
1874
+ }
1875
+ }
1876
+ };
1877
+ }
1490
1878
  export {
1879
+ HandshakeStage,
1491
1880
  InputInjector,
1492
1881
  MockPointerLock,
1493
1882
  MockTransport,
1494
1883
  captureAudioEvents,
1495
1884
  captureCanvasDrawCalls,
1885
+ captureGameScreenshot,
1496
1886
  captureGameState,
1887
+ compareScreenshots,
1497
1888
  createBinaryStreamMock,
1498
1889
  createBinaryWriterMock,
1499
1890
  createControlledTimer,
1891
+ createCustomNetworkCondition,
1892
+ createDeltaSnapshot,
1500
1893
  createEntity,
1501
1894
  createEntityStateFactory,
1502
1895
  createGameStateSnapshotFactory,
1503
1896
  createMockAudioContext,
1504
1897
  createMockCanvas,
1505
1898
  createMockCanvasContext2D,
1899
+ createMockConnection,
1506
1900
  createMockEngine,
1507
1901
  createMockGame,
1508
1902
  createMockGameState,
1903
+ createMockHandshake,
1509
1904
  createMockImage,
1510
1905
  createMockImageData,
1511
1906
  createMockIndexedDB,
@@ -1520,13 +1915,17 @@ export {
1520
1915
  createMockSessionStorage,
1521
1916
  createMockTransport,
1522
1917
  createMockUDPSocket,
1918
+ createMockUserInfo,
1523
1919
  createMockWebGL2Context,
1920
+ createMultiplayerTestScenario,
1524
1921
  createNetChanMock,
1525
1922
  createPlayerStateFactory,
1526
1923
  createPlaywrightTestClient,
1924
+ createServerSnapshot,
1527
1925
  createSpawnContext,
1528
1926
  createStorageTestScenario,
1529
1927
  createTestContext,
1928
+ createVisualTestScenario,
1530
1929
  intersects,
1531
1930
  ladderTrace,
1532
1931
  makeAxisBrush,
@@ -1536,14 +1935,24 @@ export {
1536
1935
  makeLeafModel,
1537
1936
  makeNode,
1538
1937
  makePlane,
1938
+ serializeUserInfo,
1539
1939
  setupBrowserEnvironment,
1540
1940
  setupMockAudioContext,
1541
1941
  setupNodeEnvironment,
1542
1942
  simulateFrames,
1543
1943
  simulateFramesWithMock,
1944
+ simulateHandshake,
1945
+ simulateNetworkCondition,
1946
+ simulatePlayerInput,
1947
+ simulatePlayerJoin,
1948
+ simulatePlayerLeave,
1949
+ simulateServerTick,
1950
+ simulateSnapshotDelivery,
1544
1951
  stairTrace,
1545
1952
  teardownBrowserEnvironment,
1546
1953
  teardownMockAudioContext,
1954
+ throttleBandwidth,
1955
+ verifySnapshotConsistency,
1547
1956
  waitForGameReady
1548
1957
  };
1549
1958
  //# sourceMappingURL=index.js.map