Files
shacraft-core/client/tests/player-interpolation.test.js
Emil c7e86663d8
MVP checks / mvp (push) Canceled after 0s
Expand voxel gameplay, lighting, full-height streaming and world imports
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
2026-09-17 02:10:53 +03:00

141 lines
4.3 KiB
JavaScript

import test from "node:test";
import assert from "node:assert/strict";
import {
updateRemotePlayer,
smoothRemotePlayer,
} from "../player-interpolation.js";
const radians = (degrees) => (degrees * Math.PI) / 180;
const near = (actual, expected, tolerance = 1e-11) =>
assert.ok(
Math.abs(actual - expected) < tolerance,
`Expected ${actual} to be within ${tolerance} of ${expected}`,
);
test("network updates change targets without snapping the rendered pose", () => {
const previous = updateRemotePlayer(null, {
id: "remote",
position: [1, 2, 3],
yaw: 0.2,
name: "Before",
});
const packet = {
id: "remote",
position: [5, 6, 7],
yaw: 1.2,
name: "After",
};
const player = updateRemotePlayer(previous, packet);
assert.deepEqual(player.position, [1, 2, 3]);
assert.equal(player.yaw, 0.2);
assert.deepEqual(player.target, [5, 6, 7]);
assert.equal(player.targetYaw, 1.2);
assert.equal(player.name, "After");
smoothRemotePlayer(player, 0.25);
assert.deepEqual(player.position, [2, 3, 4]);
near(player.yaw, 0.45);
assert.deepEqual(player.target, [5, 6, 7]);
assert.equal(player.targetYaw, 1.2);
assert.deepEqual(packet.position, [5, 6, 7]);
});
test("a packet arriving mid-turn continues from the current rendered angle", () => {
let player = updateRemotePlayer(null, { position: [0, 0, 0], yaw: 0 });
player = updateRemotePlayer(player, { position: [4, 0, 0], yaw: 1 });
smoothRemotePlayer(player, 0.5);
player = updateRemotePlayer(player, { position: [8, 0, 0], yaw: 2 });
assert.deepEqual(player.position, [2, 0, 0]);
near(player.yaw, 0.5);
smoothRemotePlayer(player, 0.5);
assert.deepEqual(player.position, [5, 0, 0]);
near(player.yaw, 1.25);
});
test("turns across the angle boundary follow the short arc in both directions", () => {
for (const direction of [1, -1]) {
let player = updateRemotePlayer(null, {
position: [0, 0, 0],
yaw: radians(179 * direction),
});
player = updateRemotePlayer(player, {
position: [0, 0, 0],
yaw: radians(-179 * direction),
});
smoothRemotePlayer(player, 0.5);
near(player.yaw, radians(180 * direction));
smoothRemotePlayer(player, 0.5);
near(player.yaw, radians(180.5 * direction));
}
});
test("unbounded yaw values and equivalent full turns do not cause long spins", () => {
let player = updateRemotePlayer(null, {
position: [0, 0, 0],
yaw: 30 * Math.PI + 0.2,
});
player = updateRemotePlayer(player, {
position: [0, 0, 0],
yaw: -18 * Math.PI + 0.6,
});
smoothRemotePlayer(player, 0.5);
near(player.yaw, 30 * Math.PI + 0.4);
player = updateRemotePlayer(player, {
position: [0, 0, 0],
yaw: player.yaw + 8 * Math.PI,
});
const before = player.yaw;
smoothRemotePlayer(player, 0.5);
near(player.yaw, before);
});
test("initial and instant updates initialize both the rendered pose and targets", () => {
const first = updateRemotePlayer(undefined, { position: [3, 4, 5] });
assert.deepEqual(first.position, [3, 4, 5]);
assert.deepEqual(first.target, [3, 4, 5]);
assert.equal(first.yaw, 0);
assert.equal(first.targetYaw, 0);
const instant = updateRemotePlayer(
first,
{ position: [20, 30, 40], yaw: -2.5 },
true,
);
assert.deepEqual(instant.position, [20, 30, 40]);
assert.deepEqual(instant.target, [20, 30, 40]);
assert.equal(instant.yaw, -2.5);
assert.equal(instant.targetYaw, -2.5);
smoothRemotePlayer(instant, 0.75);
assert.deepEqual(instant.position, [20, 30, 40]);
assert.equal(instant.yaw, -2.5);
});
test("exponential smoothing has the same result at 30, 60, and 144 fps", () => {
const elapsed = 0.5;
const remaining = Math.exp(-elapsed * 19);
for (const fps of [30, 60, 144]) {
let player = updateRemotePlayer(null, {
position: [1, 2, 3],
yaw: radians(179),
});
player = updateRemotePlayer(player, {
position: [11, 22, 33],
yaw: radians(-179),
});
const blend = 1 - Math.exp(-19 / fps);
for (let frame = 0; frame < elapsed * fps; frame++) {
smoothRemotePlayer(player, blend);
}
near(player.yaw, radians(181) - radians(2) * remaining);
for (let axis = 0; axis < 3; axis++) {
const start = axis + 1;
const target = (axis + 1) * 11;
near(player.position[axis], target - (target - start) * remaining);
}
}
});