/** * The view angle belongs to the browser between snapshots, and the server * gets it back only when it has something to say that we did not. */ import { expect, test } from "bun:test"; import { makeYaw, settle, shortWay, told, turn, SENT_TOLERANCE } from "../static/yaw.js"; const me = (angle: number, extra: Partial<{ id: string; alive: boolean }> = {}) => ({ id: "p1", alive: true, angle, ...extra, }); // The server rounds an angle to a tenth of a radian on the way out. const echoed = (angle: number) => Math.round(angle * 10) / 10; test("the first snapshot is where you are looking: there is no view of your own yet", () => { const yaw = makeYaw(); expect(yaw.ready).toBe(false); expect(settle(yaw, me(1.4), 10)).toBeCloseTo(1.4, 6); expect(yaw.ready).toBe(true); }); test("a snapshot does not drag the camera back to the angle it was told", () => { const yaw = makeYaw(); settle(yaw, me(0), 10); // A flick: the mouse moves at frame rate, and every angle is announced. for (let i = 1; i <= 10; i++) { turn(yaw, 0.12); told(yaw, yaw.angle, 10 + i * 0.016); } expect(yaw.angle).toBeCloseTo(1.2, 6); // The snapshot is the angle the server last heard — four frames ago. settle(yaw, me(echoed(0.72)), 10.17); expect(yaw.angle).toBeCloseTo(1.2, 6); }); test("an angle we never sent is a correction, and is taken", () => { const yaw = makeYaw(); settle(yaw, me(0), 10); told(yaw, 0, 10); turn(yaw, 0.5); told(yaw, 0.5, 10.05); // The round starts and the match points everybody at the site. settle(yaw, me(-2.1), 10.1); expect(yaw.angle).toBeCloseTo(-2.1, 6); }); test("an echo just inside the rounding is ours, one just outside is not", () => { const near = makeYaw(); settle(near, me(0), 10); told(near, 0.5, 10); turn(near, 0.4); settle(near, me(0.5 + SENT_TOLERANCE / 2), 10.05); expect(near.angle).toBeCloseTo(0.4, 6); const far = makeYaw(); settle(far, me(0), 10); told(far, 0.5, 10); turn(far, 0.4); settle(far, me(0.5 + SENT_TOLERANCE * 2), 10.05); expect(far.angle).toBeCloseTo(0.5 + SENT_TOLERANCE * 2, 6); }); test("an echo from a second ago has expired: only recent angles are ours", () => { const yaw = makeYaw(); settle(yaw, me(0), 10); told(yaw, 0.5, 10); turn(yaw, 0.4); settle(yaw, me(0.5), 12); expect(yaw.angle).toBeCloseTo(0.5, 6); }); test("a client that has not spoken yet keeps what it has", () => { const yaw = makeYaw(); settle(yaw, me(0), 10); turn(yaw, 0.8); // No input has been sent between the two snapshots; the server can only be // repeating what it already knew, so there is nothing here to take. settle(yaw, me(0), 10.03); expect(yaw.angle).toBeCloseTo(0.8, 6); }); test("a respawn and a seat change both point you where the server points you", () => { const respawn = makeYaw(); settle(respawn, me(0, { alive: false }), 10); told(respawn, 0, 10); turn(respawn, 1); settle(respawn, me(0, { alive: true }), 10.1); expect(respawn.angle).toBeCloseTo(0, 6); const seat = makeYaw(); settle(seat, me(0), 10); told(seat, 0, 10); turn(seat, 1); settle(seat, { id: "p2", alive: true, angle: 0 }, 10.1); expect(seat.angle).toBeCloseTo(0, 6); }); test("the wrap is taken the short way round", () => { expect(shortWay(3.0, -3.0)).toBeCloseTo(0.2831853, 6); expect(shortWay(-3.0, 3.0)).toBeCloseTo(-0.2831853, 6); // Which means an echo either side of π is still ours. const yaw = makeYaw(); settle(yaw, me(0), 10); told(yaw, Math.PI - 0.01, 10); turn(yaw, 0.3); settle(yaw, me(-Math.PI + 0.01), 10.05); expect(yaw.angle).toBeCloseTo(0.3, 6); }); test("the sent log does not grow without bound", () => { const yaw = makeYaw(); for (let i = 0; i < 500; i++) told(yaw, i * 0.01, 10 + i * 0.05); expect(yaw.sent.length).toBeLessThan(30); });