MVP checks / mvp (push) Canceled after 0s
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.
141 lines
4.3 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
});
|