@quake2ts/test-utils 0.0.805 → 0.0.808
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 +234 -164
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +46 -16
- package/dist/index.d.ts +46 -16
- package/dist/index.js +208 -143
- package/dist/index.js.map +1 -1
- package/package.json +9 -9
- package/src/engine/helpers/webgl-playwright.ts +76 -8
- package/src/engine/renderers.ts +31 -0
- package/src/index.ts +1 -0
package/dist/index.cjs
CHANGED
|
@@ -88,6 +88,7 @@ __export(index_exports, {
|
|
|
88
88
|
createInputInjector: () => createInputInjector,
|
|
89
89
|
createInterpolationTestData: () => createInterpolationTestData,
|
|
90
90
|
createItemEntityFactory: () => createItemEntityFactory,
|
|
91
|
+
createLoggingRenderer: () => createLoggingRenderer,
|
|
91
92
|
createMessageReaderMock: () => createMessageReaderMock,
|
|
92
93
|
createMessageWriterMock: () => createMessageWriterMock,
|
|
93
94
|
createMockAI: () => createMockAI,
|
|
@@ -206,6 +207,7 @@ __export(index_exports, {
|
|
|
206
207
|
createMonsterEntityFactory: () => createMonsterEntityFactory,
|
|
207
208
|
createMultiplayerTestScenario: () => createMultiplayerTestScenario,
|
|
208
209
|
createNetChanMock: () => createNetChanMock,
|
|
210
|
+
createNullRenderer: () => createNullRenderer,
|
|
209
211
|
createPacketMock: () => createPacketMock,
|
|
210
212
|
createPhysicsTestContext: () => createPhysicsTestContext,
|
|
211
213
|
createPhysicsTestScenario: () => createPhysicsTestScenario,
|
|
@@ -233,6 +235,8 @@ __export(index_exports, {
|
|
|
233
235
|
createWebGLPlaywrightSetup: () => createWebGLPlaywrightSetup,
|
|
234
236
|
createWebGLRenderTestSetup: () => createWebGLRenderTestSetup,
|
|
235
237
|
expectAnimationSnapshot: () => expectAnimationSnapshot,
|
|
238
|
+
expectNoDoubleTransform: () => expectNoDoubleTransform,
|
|
239
|
+
expectRendererCalls: () => expectRendererCalls,
|
|
236
240
|
expectSnapshot: () => expectSnapshot,
|
|
237
241
|
findPakFile: () => findPakFile,
|
|
238
242
|
flipPixelsVertically: () => flipPixelsVertically,
|
|
@@ -292,6 +296,7 @@ __export(index_exports, {
|
|
|
292
296
|
teardownNodeEnvironment: () => teardownNodeEnvironment,
|
|
293
297
|
testComputeShader: () => testComputeShader,
|
|
294
298
|
testPipelineRendering: () => testPipelineRendering,
|
|
299
|
+
testWebGLAnimation: () => testWebGLAnimation,
|
|
295
300
|
testWebGLRenderer: () => testWebGLRenderer,
|
|
296
301
|
throttleBandwidth: () => throttleBandwidth,
|
|
297
302
|
verifySmoothing: () => verifySmoothing,
|
|
@@ -4543,9 +4548,153 @@ async function renderAndExpectSnapshot(setup, renderFn, options) {
|
|
|
4543
4548
|
});
|
|
4544
4549
|
}
|
|
4545
4550
|
|
|
4551
|
+
// src/visual/animation-snapshots.ts
|
|
4552
|
+
var import_upng_js = __toESM(require("upng-js"), 1);
|
|
4553
|
+
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
4554
|
+
var import_fs2 = require("fs");
|
|
4555
|
+
var import_path2 = __toESM(require("path"), 1);
|
|
4556
|
+
async function loadAPNG(filepath) {
|
|
4557
|
+
const buffer = await import_promises2.default.readFile(filepath);
|
|
4558
|
+
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
4559
|
+
const img = import_upng_js.default.decode(arrayBuffer);
|
|
4560
|
+
const framesRGBA = import_upng_js.default.toRGBA8(img);
|
|
4561
|
+
const frames = framesRGBA.map((buffer2) => new Uint8ClampedArray(buffer2));
|
|
4562
|
+
return {
|
|
4563
|
+
width: img.width,
|
|
4564
|
+
height: img.height,
|
|
4565
|
+
frames
|
|
4566
|
+
};
|
|
4567
|
+
}
|
|
4568
|
+
async function saveAPNG(filepath, frames, width, height, delayMs) {
|
|
4569
|
+
const buffers = frames.map((f) => {
|
|
4570
|
+
const dst = new ArrayBuffer(f.byteLength);
|
|
4571
|
+
new Uint8ClampedArray(dst).set(f);
|
|
4572
|
+
return dst;
|
|
4573
|
+
});
|
|
4574
|
+
const delays = new Array(frames.length).fill(delayMs);
|
|
4575
|
+
const pngBuffer = import_upng_js.default.encode(buffers, width, height, 0, delays);
|
|
4576
|
+
await import_promises2.default.mkdir(import_path2.default.dirname(filepath), { recursive: true });
|
|
4577
|
+
await import_promises2.default.writeFile(filepath, Buffer.from(pngBuffer));
|
|
4578
|
+
}
|
|
4579
|
+
async function expectAnimationSnapshot(renderAndCaptureFrame, options) {
|
|
4580
|
+
const {
|
|
4581
|
+
name,
|
|
4582
|
+
width,
|
|
4583
|
+
height,
|
|
4584
|
+
frameCount,
|
|
4585
|
+
fps = 10,
|
|
4586
|
+
updateBaseline = false,
|
|
4587
|
+
snapshotDir = import_path2.default.join(process.cwd(), "tests", "__snapshots__"),
|
|
4588
|
+
threshold = 0.1,
|
|
4589
|
+
maxDifferencePercent = 0.1
|
|
4590
|
+
} = options;
|
|
4591
|
+
if (!width || !height) {
|
|
4592
|
+
throw new Error("Width and height are required for expectAnimationSnapshot");
|
|
4593
|
+
}
|
|
4594
|
+
const baselinePath = getSnapshotPath(name, "baseline", snapshotDir);
|
|
4595
|
+
const actualPath = getSnapshotPath(name, "actual", snapshotDir);
|
|
4596
|
+
const diffPath = getSnapshotPath(name, "diff", snapshotDir);
|
|
4597
|
+
const alwaysSave = process.env.ALWAYS_SAVE_SNAPSHOTS === "1";
|
|
4598
|
+
const actualFrames = [];
|
|
4599
|
+
for (let i = 0; i < frameCount; i++) {
|
|
4600
|
+
const frameData = await renderAndCaptureFrame(i);
|
|
4601
|
+
if (frameData.length !== width * height * 4) {
|
|
4602
|
+
throw new Error(`Frame ${i} dimension mismatch: expected length ${width * height * 4}, got ${frameData.length}`);
|
|
4603
|
+
}
|
|
4604
|
+
actualFrames.push(frameData);
|
|
4605
|
+
}
|
|
4606
|
+
const delayMs = 1e3 / fps;
|
|
4607
|
+
let baselineFrames = null;
|
|
4608
|
+
let shouldUpdateBaseline = updateBaseline || !(0, import_fs2.existsSync)(baselinePath);
|
|
4609
|
+
if (!shouldUpdateBaseline) {
|
|
4610
|
+
try {
|
|
4611
|
+
const baseline = await loadAPNG(baselinePath);
|
|
4612
|
+
if (baseline.width !== width || baseline.height !== height) {
|
|
4613
|
+
console.warn(`Baseline dimensions mismatch (${baseline.width}x${baseline.height} vs ${width}x${height}). Forcing update.`);
|
|
4614
|
+
shouldUpdateBaseline = true;
|
|
4615
|
+
} else if (baseline.frames.length !== frameCount) {
|
|
4616
|
+
console.warn(`Baseline frame count mismatch (${baseline.frames.length} vs ${frameCount}). Forcing update.`);
|
|
4617
|
+
shouldUpdateBaseline = true;
|
|
4618
|
+
} else {
|
|
4619
|
+
baselineFrames = baseline.frames;
|
|
4620
|
+
}
|
|
4621
|
+
} catch (e) {
|
|
4622
|
+
console.warn(`Failed to load baseline APNG: ${e}. Forcing update.`);
|
|
4623
|
+
shouldUpdateBaseline = true;
|
|
4624
|
+
}
|
|
4625
|
+
}
|
|
4626
|
+
if (shouldUpdateBaseline) {
|
|
4627
|
+
console.log(`Creating/Updating baseline for ${name} at ${baselinePath}`);
|
|
4628
|
+
await saveAPNG(baselinePath, actualFrames, width, height, delayMs);
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
if (!baselineFrames) {
|
|
4632
|
+
throw new Error("Baseline frames missing despite checks.");
|
|
4633
|
+
}
|
|
4634
|
+
const frameStats = [];
|
|
4635
|
+
const diffFrames = [];
|
|
4636
|
+
let totalDiffPixels = 0;
|
|
4637
|
+
let totalPixels = 0;
|
|
4638
|
+
for (let i = 0; i < frameCount; i++) {
|
|
4639
|
+
const result2 = await compareSnapshots(actualFrames[i], baselineFrames[i], width, height, options);
|
|
4640
|
+
frameStats.push(result2);
|
|
4641
|
+
if (result2.diffImage) {
|
|
4642
|
+
diffFrames.push(result2.diffImage);
|
|
4643
|
+
} else {
|
|
4644
|
+
diffFrames.push(new Uint8ClampedArray(width * height * 4));
|
|
4645
|
+
}
|
|
4646
|
+
totalDiffPixels += result2.pixelsDifferent;
|
|
4647
|
+
totalPixels += width * height;
|
|
4648
|
+
}
|
|
4649
|
+
const avgPercentDifferent = totalDiffPixels / totalPixels * 100;
|
|
4650
|
+
const passed = avgPercentDifferent <= (maxDifferencePercent || 0);
|
|
4651
|
+
const result = {
|
|
4652
|
+
passed,
|
|
4653
|
+
totalPixels,
|
|
4654
|
+
totalDiffPixels,
|
|
4655
|
+
percentDifferent: avgPercentDifferent,
|
|
4656
|
+
frameStats
|
|
4657
|
+
};
|
|
4658
|
+
const statsPath = import_path2.default.join(snapshotDir, "stats", `${name}.json`);
|
|
4659
|
+
await import_promises2.default.mkdir(import_path2.default.dirname(statsPath), { recursive: true });
|
|
4660
|
+
await import_promises2.default.writeFile(statsPath, JSON.stringify({
|
|
4661
|
+
passed: result.passed,
|
|
4662
|
+
percentDifferent: result.percentDifferent,
|
|
4663
|
+
pixelsDifferent: result.totalDiffPixels,
|
|
4664
|
+
totalPixels: result.totalPixels,
|
|
4665
|
+
threshold: options.threshold ?? 0.1,
|
|
4666
|
+
maxDifferencePercent: options.maxDifferencePercent ?? 0.1,
|
|
4667
|
+
frameCount
|
|
4668
|
+
}, null, 2));
|
|
4669
|
+
if (!passed || alwaysSave) {
|
|
4670
|
+
await saveAPNG(actualPath, actualFrames, width, height, delayMs);
|
|
4671
|
+
await saveAPNG(diffPath, diffFrames, width, height, delayMs);
|
|
4672
|
+
}
|
|
4673
|
+
if (!passed) {
|
|
4674
|
+
const failThreshold = 10;
|
|
4675
|
+
const errorMessage = `Animation snapshot comparison failed for ${name}: ${result.percentDifferent.toFixed(2)}% different (${result.totalDiffPixels} pixels total). See ${diffPath} for details.`;
|
|
4676
|
+
if (result.percentDifferent <= failThreshold) {
|
|
4677
|
+
console.warn(`[WARNING] ${errorMessage} (Marked as failed in report but passing test execution due to <${failThreshold}% difference)`);
|
|
4678
|
+
} else {
|
|
4679
|
+
throw new Error(errorMessage);
|
|
4680
|
+
}
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
|
|
4546
4684
|
// src/engine/helpers/webgl-playwright.ts
|
|
4547
4685
|
var import_http = require("http");
|
|
4548
|
-
var
|
|
4686
|
+
var import_path3 = __toESM(require("path"), 1);
|
|
4687
|
+
var import_fs3 = __toESM(require("fs"), 1);
|
|
4688
|
+
function findWorkspaceRoot(startDir) {
|
|
4689
|
+
let currentDir = startDir;
|
|
4690
|
+
while (currentDir !== import_path3.default.parse(currentDir).root) {
|
|
4691
|
+
if (import_fs3.default.existsSync(import_path3.default.join(currentDir, "pnpm-workspace.yaml"))) {
|
|
4692
|
+
return currentDir;
|
|
4693
|
+
}
|
|
4694
|
+
currentDir = import_path3.default.dirname(currentDir);
|
|
4695
|
+
}
|
|
4696
|
+
return process.cwd();
|
|
4697
|
+
}
|
|
4549
4698
|
async function createWebGLPlaywrightSetup(options = {}) {
|
|
4550
4699
|
const width = options.width ?? 256;
|
|
4551
4700
|
const height = options.height ?? 256;
|
|
@@ -4564,7 +4713,7 @@ async function createWebGLPlaywrightSetup(options = {}) {
|
|
|
4564
4713
|
} catch (e) {
|
|
4565
4714
|
throw new Error('Failed to load "serve-handler" package. Please ensure it is installed.');
|
|
4566
4715
|
}
|
|
4567
|
-
const repoRoot =
|
|
4716
|
+
const repoRoot = findWorkspaceRoot(__dirname);
|
|
4568
4717
|
const staticServer = (0, import_http.createServer)((request, response) => {
|
|
4569
4718
|
return handler(request, response, {
|
|
4570
4719
|
public: repoRoot,
|
|
@@ -4628,9 +4777,9 @@ async function createWebGLPlaywrightSetup(options = {}) {
|
|
|
4628
4777
|
cleanup
|
|
4629
4778
|
};
|
|
4630
4779
|
}
|
|
4631
|
-
async function renderAndCaptureWebGLPlaywright(page, renderFn, width, height) {
|
|
4780
|
+
async function renderAndCaptureWebGLPlaywright(page, renderFn, width, height, frameIndex = 0) {
|
|
4632
4781
|
try {
|
|
4633
|
-
const pixelData = await page.evaluate(({ code, width: width2, height: height2 }) => {
|
|
4782
|
+
const pixelData = await page.evaluate(({ code, width: width2, height: height2, frameIndex: frameIndex2 }) => {
|
|
4634
4783
|
const renderer = window.testRenderer;
|
|
4635
4784
|
const gl = window.testGl;
|
|
4636
4785
|
const canvas = window.testCanvas;
|
|
@@ -4643,8 +4792,8 @@ async function renderAndCaptureWebGLPlaywright(page, renderFn, width, height) {
|
|
|
4643
4792
|
gl.viewport(0, 0, width2, height2);
|
|
4644
4793
|
}
|
|
4645
4794
|
try {
|
|
4646
|
-
const fn = new Function("renderer", "gl", code);
|
|
4647
|
-
fn(renderer, gl);
|
|
4795
|
+
const fn = new Function("renderer", "gl", "frameIndex", code);
|
|
4796
|
+
fn(renderer, gl, frameIndex2);
|
|
4648
4797
|
} catch (err) {
|
|
4649
4798
|
throw new Error(`Renderer Execution Error: ${err.message}
|
|
4650
4799
|
Code:
|
|
@@ -4652,7 +4801,7 @@ ${code}`);
|
|
|
4652
4801
|
}
|
|
4653
4802
|
gl.finish();
|
|
4654
4803
|
return window.captureCanvas();
|
|
4655
|
-
}, { code: renderFn, width, height });
|
|
4804
|
+
}, { code: renderFn, width, height, frameIndex });
|
|
4656
4805
|
return new Uint8ClampedArray(pixelData);
|
|
4657
4806
|
} catch (err) {
|
|
4658
4807
|
throw new Error(`Browser Test Error: ${err.message}`);
|
|
@@ -4679,6 +4828,33 @@ async function testWebGLRenderer(renderCode, options) {
|
|
|
4679
4828
|
await setup.cleanup();
|
|
4680
4829
|
}
|
|
4681
4830
|
}
|
|
4831
|
+
async function testWebGLAnimation(renderCode, options) {
|
|
4832
|
+
const setup = await createWebGLPlaywrightSetup(options);
|
|
4833
|
+
try {
|
|
4834
|
+
await expectAnimationSnapshot(async (frameIndex) => {
|
|
4835
|
+
return renderAndCaptureWebGLPlaywright(
|
|
4836
|
+
setup.page,
|
|
4837
|
+
renderCode,
|
|
4838
|
+
options.width,
|
|
4839
|
+
options.height,
|
|
4840
|
+
frameIndex
|
|
4841
|
+
);
|
|
4842
|
+
}, {
|
|
4843
|
+
name: options.name,
|
|
4844
|
+
description: options.description,
|
|
4845
|
+
width: setup.width,
|
|
4846
|
+
height: setup.height,
|
|
4847
|
+
frameCount: options.frameCount,
|
|
4848
|
+
fps: options.fps,
|
|
4849
|
+
updateBaseline: options.updateBaseline,
|
|
4850
|
+
snapshotDir: options.snapshotDir,
|
|
4851
|
+
threshold: options.threshold,
|
|
4852
|
+
maxDifferencePercent: options.maxDifferencePercent
|
|
4853
|
+
});
|
|
4854
|
+
} finally {
|
|
4855
|
+
await setup.cleanup();
|
|
4856
|
+
}
|
|
4857
|
+
}
|
|
4682
4858
|
|
|
4683
4859
|
// src/engine/helpers/pipeline-test-template.ts
|
|
4684
4860
|
var import_vitest18 = require("vitest");
|
|
@@ -4778,9 +4954,31 @@ function createSolidTexture(width, height, color) {
|
|
|
4778
4954
|
return data;
|
|
4779
4955
|
}
|
|
4780
4956
|
|
|
4957
|
+
// src/engine/renderers.ts
|
|
4958
|
+
var import_engine8 = require("@quake2ts/engine");
|
|
4959
|
+
var import_vitest19 = require("vitest");
|
|
4960
|
+
function createNullRenderer(width = 800, height = 600) {
|
|
4961
|
+
return new import_engine8.NullRenderer(width, height);
|
|
4962
|
+
}
|
|
4963
|
+
function createLoggingRenderer(targetSystem = import_engine8.CoordinateSystem.QUAKE, options) {
|
|
4964
|
+
return new import_engine8.LoggingRenderer({
|
|
4965
|
+
targetSystem,
|
|
4966
|
+
...options
|
|
4967
|
+
});
|
|
4968
|
+
}
|
|
4969
|
+
function expectRendererCalls(renderer, expectedCalls) {
|
|
4970
|
+
const actualCalls = renderer.getCallLog();
|
|
4971
|
+
(0, import_vitest19.expect)(actualCalls).toEqual(expectedCalls);
|
|
4972
|
+
}
|
|
4973
|
+
function expectNoDoubleTransform(renderer) {
|
|
4974
|
+
const logs = renderer.getLogs();
|
|
4975
|
+
const warnings = logs.filter((log) => log.includes("double-transform"));
|
|
4976
|
+
(0, import_vitest19.expect)(warnings).toHaveLength(0);
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4781
4979
|
// src/client/helpers/view.ts
|
|
4782
4980
|
var import_gl_matrix = require("gl-matrix");
|
|
4783
|
-
var
|
|
4981
|
+
var import_engine9 = require("@quake2ts/engine");
|
|
4784
4982
|
function toVec3(v) {
|
|
4785
4983
|
if (v instanceof Float32Array && v.length === 3) {
|
|
4786
4984
|
return v;
|
|
@@ -4794,7 +4992,7 @@ function toVec3(v) {
|
|
|
4794
4992
|
return import_gl_matrix.vec3.create();
|
|
4795
4993
|
}
|
|
4796
4994
|
function createMockCamera(overrides = {}) {
|
|
4797
|
-
const camera = new
|
|
4995
|
+
const camera = new import_engine9.Camera();
|
|
4798
4996
|
if (overrides.position) {
|
|
4799
4997
|
camera.position = toVec3(overrides.position);
|
|
4800
4998
|
}
|
|
@@ -4882,7 +5080,7 @@ function simulateCameraMovement(camera, input, deltaTime) {
|
|
|
4882
5080
|
}
|
|
4883
5081
|
|
|
4884
5082
|
// src/client/helpers/hud.ts
|
|
4885
|
-
var
|
|
5083
|
+
var import_vitest20 = require("vitest");
|
|
4886
5084
|
function createMockHudState(overrides) {
|
|
4887
5085
|
const defaultPs = {
|
|
4888
5086
|
damageAlpha: 0,
|
|
@@ -4912,11 +5110,11 @@ function createMockHudState(overrides) {
|
|
|
4912
5110
|
defaultStats[2] = 25;
|
|
4913
5111
|
defaultStats[4] = 50;
|
|
4914
5112
|
const defaultMessages = {
|
|
4915
|
-
drawCenterPrint:
|
|
4916
|
-
drawNotifications:
|
|
4917
|
-
addCenterPrint:
|
|
4918
|
-
addNotification:
|
|
4919
|
-
clear:
|
|
5113
|
+
drawCenterPrint: import_vitest20.vi.fn(),
|
|
5114
|
+
drawNotifications: import_vitest20.vi.fn(),
|
|
5115
|
+
addCenterPrint: import_vitest20.vi.fn(),
|
|
5116
|
+
addNotification: import_vitest20.vi.fn(),
|
|
5117
|
+
clear: import_vitest20.vi.fn()
|
|
4920
5118
|
};
|
|
4921
5119
|
return {
|
|
4922
5120
|
ps: overrides?.ps ?? defaultPs,
|
|
@@ -4935,7 +5133,7 @@ function createMockHudState(overrides) {
|
|
|
4935
5133
|
function createMockScoreboard(players = []) {
|
|
4936
5134
|
return {
|
|
4937
5135
|
players,
|
|
4938
|
-
draw:
|
|
5136
|
+
draw: import_vitest20.vi.fn()
|
|
4939
5137
|
};
|
|
4940
5138
|
}
|
|
4941
5139
|
function createMockChatMessage(text, sender, timestamp = Date.now()) {
|
|
@@ -4954,7 +5152,7 @@ function createMockNotification(type, message, duration = 3e3) {
|
|
|
4954
5152
|
}
|
|
4955
5153
|
|
|
4956
5154
|
// src/client/mocks/network.ts
|
|
4957
|
-
var
|
|
5155
|
+
var import_engine10 = require("@quake2ts/engine");
|
|
4958
5156
|
function createMockServerMessage(type, data = new Uint8Array()) {
|
|
4959
5157
|
return { type, data };
|
|
4960
5158
|
}
|
|
@@ -4966,7 +5164,7 @@ function createMockSnapshot(serverFrame, entities = [], playerState, deltaFrame
|
|
|
4966
5164
|
areaBytes: 0,
|
|
4967
5165
|
areaBits: new Uint8Array(),
|
|
4968
5166
|
playerState: {
|
|
4969
|
-
...(0,
|
|
5167
|
+
...(0, import_engine10.createEmptyProtocolPlayerState)(),
|
|
4970
5168
|
...playerState
|
|
4971
5169
|
},
|
|
4972
5170
|
packetEntities: {
|
|
@@ -4983,7 +5181,7 @@ function createMockDeltaFrame(serverFrame, deltaFrame, entities = [], playerStat
|
|
|
4983
5181
|
areaBytes: 0,
|
|
4984
5182
|
areaBits: new Uint8Array(),
|
|
4985
5183
|
playerState: {
|
|
4986
|
-
...(0,
|
|
5184
|
+
...(0, import_engine10.createEmptyProtocolPlayerState)(),
|
|
4987
5185
|
...playerState
|
|
4988
5186
|
},
|
|
4989
5187
|
packetEntities: {
|
|
@@ -5003,7 +5201,7 @@ function simulatePacketLoss(messages, lossPercent) {
|
|
|
5003
5201
|
return messages.filter(() => Math.random() * 100 >= lossPercent);
|
|
5004
5202
|
}
|
|
5005
5203
|
function createMockEntityState(number, modelIndex = 0, origin = { x: 0, y: 0, z: 0 }, overrides) {
|
|
5006
|
-
const state = (0,
|
|
5204
|
+
const state = (0, import_engine10.createEmptyEntityState)();
|
|
5007
5205
|
state.number = number;
|
|
5008
5206
|
state.modelindex = modelIndex;
|
|
5009
5207
|
state.origin.x = origin.x ?? 0;
|
|
@@ -5034,12 +5232,12 @@ function createMockFogData(overrides = {}) {
|
|
|
5034
5232
|
}
|
|
5035
5233
|
|
|
5036
5234
|
// src/client/mocks/download.ts
|
|
5037
|
-
var
|
|
5235
|
+
var import_vitest21 = require("vitest");
|
|
5038
5236
|
function createMockDownloadManager(overrides) {
|
|
5039
5237
|
return {
|
|
5040
|
-
download:
|
|
5041
|
-
cancel:
|
|
5042
|
-
getProgress:
|
|
5238
|
+
download: import_vitest21.vi.fn().mockResolvedValue(new ArrayBuffer(0)),
|
|
5239
|
+
cancel: import_vitest21.vi.fn(),
|
|
5240
|
+
getProgress: import_vitest21.vi.fn().mockReturnValue(0),
|
|
5043
5241
|
...overrides
|
|
5044
5242
|
};
|
|
5045
5243
|
}
|
|
@@ -5148,20 +5346,20 @@ var createMockConnectionState = (state = "connected") => ({
|
|
|
5148
5346
|
});
|
|
5149
5347
|
|
|
5150
5348
|
// src/client/mocks/console.ts
|
|
5151
|
-
var
|
|
5349
|
+
var import_vitest22 = require("vitest");
|
|
5152
5350
|
function createMockConsole(overrides) {
|
|
5153
5351
|
const history = [];
|
|
5154
5352
|
const errors = [];
|
|
5155
5353
|
const commands = {};
|
|
5156
5354
|
const cvars = {};
|
|
5157
5355
|
return {
|
|
5158
|
-
print:
|
|
5356
|
+
print: import_vitest22.vi.fn((text) => {
|
|
5159
5357
|
history.push(text);
|
|
5160
5358
|
}),
|
|
5161
|
-
error:
|
|
5359
|
+
error: import_vitest22.vi.fn((text) => {
|
|
5162
5360
|
errors.push(text);
|
|
5163
5361
|
}),
|
|
5164
|
-
execute:
|
|
5362
|
+
execute: import_vitest22.vi.fn((text) => {
|
|
5165
5363
|
const parts = text.trim().split(/\s+/);
|
|
5166
5364
|
const cmd = parts[0];
|
|
5167
5365
|
const args = parts.slice(1);
|
|
@@ -5171,11 +5369,11 @@ function createMockConsole(overrides) {
|
|
|
5171
5369
|
history.push(`Unknown command "${cmd}"`);
|
|
5172
5370
|
}
|
|
5173
5371
|
}),
|
|
5174
|
-
addCommand:
|
|
5372
|
+
addCommand: import_vitest22.vi.fn((name, handler) => {
|
|
5175
5373
|
commands[name] = handler;
|
|
5176
5374
|
}),
|
|
5177
|
-
getCvar:
|
|
5178
|
-
setCvar:
|
|
5375
|
+
getCvar: import_vitest22.vi.fn((name) => cvars[name]),
|
|
5376
|
+
setCvar: import_vitest22.vi.fn((name, value) => {
|
|
5179
5377
|
cvars[name] = value;
|
|
5180
5378
|
}),
|
|
5181
5379
|
getHistory: () => history,
|
|
@@ -5281,139 +5479,6 @@ var verifySmoothing = (states) => {
|
|
|
5281
5479
|
};
|
|
5282
5480
|
};
|
|
5283
5481
|
|
|
5284
|
-
// src/visual/animation-snapshots.ts
|
|
5285
|
-
var import_upng_js = __toESM(require("upng-js"), 1);
|
|
5286
|
-
var import_promises2 = __toESM(require("fs/promises"), 1);
|
|
5287
|
-
var import_fs2 = require("fs");
|
|
5288
|
-
var import_path3 = __toESM(require("path"), 1);
|
|
5289
|
-
async function loadAPNG(filepath) {
|
|
5290
|
-
const buffer = await import_promises2.default.readFile(filepath);
|
|
5291
|
-
const arrayBuffer = buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength);
|
|
5292
|
-
const img = import_upng_js.default.decode(arrayBuffer);
|
|
5293
|
-
const framesRGBA = import_upng_js.default.toRGBA8(img);
|
|
5294
|
-
const frames = framesRGBA.map((buffer2) => new Uint8ClampedArray(buffer2));
|
|
5295
|
-
return {
|
|
5296
|
-
width: img.width,
|
|
5297
|
-
height: img.height,
|
|
5298
|
-
frames
|
|
5299
|
-
};
|
|
5300
|
-
}
|
|
5301
|
-
async function saveAPNG(filepath, frames, width, height, delayMs) {
|
|
5302
|
-
const buffers = frames.map((f) => {
|
|
5303
|
-
const dst = new ArrayBuffer(f.byteLength);
|
|
5304
|
-
new Uint8ClampedArray(dst).set(f);
|
|
5305
|
-
return dst;
|
|
5306
|
-
});
|
|
5307
|
-
const delays = new Array(frames.length).fill(delayMs);
|
|
5308
|
-
const pngBuffer = import_upng_js.default.encode(buffers, width, height, 0, delays);
|
|
5309
|
-
await import_promises2.default.mkdir(import_path3.default.dirname(filepath), { recursive: true });
|
|
5310
|
-
await import_promises2.default.writeFile(filepath, Buffer.from(pngBuffer));
|
|
5311
|
-
}
|
|
5312
|
-
async function expectAnimationSnapshot(renderAndCaptureFrame, options) {
|
|
5313
|
-
const {
|
|
5314
|
-
name,
|
|
5315
|
-
width,
|
|
5316
|
-
height,
|
|
5317
|
-
frameCount,
|
|
5318
|
-
fps = 10,
|
|
5319
|
-
updateBaseline = false,
|
|
5320
|
-
snapshotDir = import_path3.default.join(process.cwd(), "tests", "__snapshots__"),
|
|
5321
|
-
threshold = 0.1,
|
|
5322
|
-
maxDifferencePercent = 0.1
|
|
5323
|
-
} = options;
|
|
5324
|
-
if (!width || !height) {
|
|
5325
|
-
throw new Error("Width and height are required for expectAnimationSnapshot");
|
|
5326
|
-
}
|
|
5327
|
-
const baselinePath = getSnapshotPath(name, "baseline", snapshotDir);
|
|
5328
|
-
const actualPath = getSnapshotPath(name, "actual", snapshotDir);
|
|
5329
|
-
const diffPath = getSnapshotPath(name, "diff", snapshotDir);
|
|
5330
|
-
const alwaysSave = process.env.ALWAYS_SAVE_SNAPSHOTS === "1";
|
|
5331
|
-
const actualFrames = [];
|
|
5332
|
-
for (let i = 0; i < frameCount; i++) {
|
|
5333
|
-
const frameData = await renderAndCaptureFrame(i);
|
|
5334
|
-
if (frameData.length !== width * height * 4) {
|
|
5335
|
-
throw new Error(`Frame ${i} dimension mismatch: expected length ${width * height * 4}, got ${frameData.length}`);
|
|
5336
|
-
}
|
|
5337
|
-
actualFrames.push(frameData);
|
|
5338
|
-
}
|
|
5339
|
-
const delayMs = 1e3 / fps;
|
|
5340
|
-
let baselineFrames = null;
|
|
5341
|
-
let shouldUpdateBaseline = updateBaseline || !(0, import_fs2.existsSync)(baselinePath);
|
|
5342
|
-
if (!shouldUpdateBaseline) {
|
|
5343
|
-
try {
|
|
5344
|
-
const baseline = await loadAPNG(baselinePath);
|
|
5345
|
-
if (baseline.width !== width || baseline.height !== height) {
|
|
5346
|
-
console.warn(`Baseline dimensions mismatch (${baseline.width}x${baseline.height} vs ${width}x${height}). Forcing update.`);
|
|
5347
|
-
shouldUpdateBaseline = true;
|
|
5348
|
-
} else if (baseline.frames.length !== frameCount) {
|
|
5349
|
-
console.warn(`Baseline frame count mismatch (${baseline.frames.length} vs ${frameCount}). Forcing update.`);
|
|
5350
|
-
shouldUpdateBaseline = true;
|
|
5351
|
-
} else {
|
|
5352
|
-
baselineFrames = baseline.frames;
|
|
5353
|
-
}
|
|
5354
|
-
} catch (e) {
|
|
5355
|
-
console.warn(`Failed to load baseline APNG: ${e}. Forcing update.`);
|
|
5356
|
-
shouldUpdateBaseline = true;
|
|
5357
|
-
}
|
|
5358
|
-
}
|
|
5359
|
-
if (shouldUpdateBaseline) {
|
|
5360
|
-
console.log(`Creating/Updating baseline for ${name} at ${baselinePath}`);
|
|
5361
|
-
await saveAPNG(baselinePath, actualFrames, width, height, delayMs);
|
|
5362
|
-
return;
|
|
5363
|
-
}
|
|
5364
|
-
if (!baselineFrames) {
|
|
5365
|
-
throw new Error("Baseline frames missing despite checks.");
|
|
5366
|
-
}
|
|
5367
|
-
const frameStats = [];
|
|
5368
|
-
const diffFrames = [];
|
|
5369
|
-
let totalDiffPixels = 0;
|
|
5370
|
-
let totalPixels = 0;
|
|
5371
|
-
for (let i = 0; i < frameCount; i++) {
|
|
5372
|
-
const result2 = await compareSnapshots(actualFrames[i], baselineFrames[i], width, height, options);
|
|
5373
|
-
frameStats.push(result2);
|
|
5374
|
-
if (result2.diffImage) {
|
|
5375
|
-
diffFrames.push(result2.diffImage);
|
|
5376
|
-
} else {
|
|
5377
|
-
diffFrames.push(new Uint8ClampedArray(width * height * 4));
|
|
5378
|
-
}
|
|
5379
|
-
totalDiffPixels += result2.pixelsDifferent;
|
|
5380
|
-
totalPixels += width * height;
|
|
5381
|
-
}
|
|
5382
|
-
const avgPercentDifferent = totalDiffPixels / totalPixels * 100;
|
|
5383
|
-
const passed = avgPercentDifferent <= (maxDifferencePercent || 0);
|
|
5384
|
-
const result = {
|
|
5385
|
-
passed,
|
|
5386
|
-
totalPixels,
|
|
5387
|
-
totalDiffPixels,
|
|
5388
|
-
percentDifferent: avgPercentDifferent,
|
|
5389
|
-
frameStats
|
|
5390
|
-
};
|
|
5391
|
-
const statsPath = import_path3.default.join(snapshotDir, "stats", `${name}.json`);
|
|
5392
|
-
await import_promises2.default.mkdir(import_path3.default.dirname(statsPath), { recursive: true });
|
|
5393
|
-
await import_promises2.default.writeFile(statsPath, JSON.stringify({
|
|
5394
|
-
passed: result.passed,
|
|
5395
|
-
percentDifferent: result.percentDifferent,
|
|
5396
|
-
pixelsDifferent: result.totalDiffPixels,
|
|
5397
|
-
totalPixels: result.totalPixels,
|
|
5398
|
-
threshold: options.threshold ?? 0.1,
|
|
5399
|
-
maxDifferencePercent: options.maxDifferencePercent ?? 0.1,
|
|
5400
|
-
frameCount
|
|
5401
|
-
}, null, 2));
|
|
5402
|
-
if (!passed || alwaysSave) {
|
|
5403
|
-
await saveAPNG(actualPath, actualFrames, width, height, delayMs);
|
|
5404
|
-
await saveAPNG(diffPath, diffFrames, width, height, delayMs);
|
|
5405
|
-
}
|
|
5406
|
-
if (!passed) {
|
|
5407
|
-
const failThreshold = 10;
|
|
5408
|
-
const errorMessage = `Animation snapshot comparison failed for ${name}: ${result.percentDifferent.toFixed(2)}% different (${result.totalDiffPixels} pixels total). See ${diffPath} for details.`;
|
|
5409
|
-
if (result.percentDifferent <= failThreshold) {
|
|
5410
|
-
console.warn(`[WARNING] ${errorMessage} (Marked as failed in report but passing test execution due to <${failThreshold}% difference)`);
|
|
5411
|
-
} else {
|
|
5412
|
-
throw new Error(errorMessage);
|
|
5413
|
-
}
|
|
5414
|
-
}
|
|
5415
|
-
}
|
|
5416
|
-
|
|
5417
5482
|
// src/e2e/playwright.ts
|
|
5418
5483
|
async function createPlaywrightTestClient(options = {}) {
|
|
5419
5484
|
let playwright;
|
|
@@ -5619,6 +5684,7 @@ function createVisualTestScenario(sceneName) {
|
|
|
5619
5684
|
createInputInjector,
|
|
5620
5685
|
createInterpolationTestData,
|
|
5621
5686
|
createItemEntityFactory,
|
|
5687
|
+
createLoggingRenderer,
|
|
5622
5688
|
createMessageReaderMock,
|
|
5623
5689
|
createMessageWriterMock,
|
|
5624
5690
|
createMockAI,
|
|
@@ -5737,6 +5803,7 @@ function createVisualTestScenario(sceneName) {
|
|
|
5737
5803
|
createMonsterEntityFactory,
|
|
5738
5804
|
createMultiplayerTestScenario,
|
|
5739
5805
|
createNetChanMock,
|
|
5806
|
+
createNullRenderer,
|
|
5740
5807
|
createPacketMock,
|
|
5741
5808
|
createPhysicsTestContext,
|
|
5742
5809
|
createPhysicsTestScenario,
|
|
@@ -5764,6 +5831,8 @@ function createVisualTestScenario(sceneName) {
|
|
|
5764
5831
|
createWebGLPlaywrightSetup,
|
|
5765
5832
|
createWebGLRenderTestSetup,
|
|
5766
5833
|
expectAnimationSnapshot,
|
|
5834
|
+
expectNoDoubleTransform,
|
|
5835
|
+
expectRendererCalls,
|
|
5767
5836
|
expectSnapshot,
|
|
5768
5837
|
findPakFile,
|
|
5769
5838
|
flipPixelsVertically,
|
|
@@ -5823,6 +5892,7 @@ function createVisualTestScenario(sceneName) {
|
|
|
5823
5892
|
teardownNodeEnvironment,
|
|
5824
5893
|
testComputeShader,
|
|
5825
5894
|
testPipelineRendering,
|
|
5895
|
+
testWebGLAnimation,
|
|
5826
5896
|
testWebGLRenderer,
|
|
5827
5897
|
throttleBandwidth,
|
|
5828
5898
|
verifySmoothing,
|