// The shared band clock: bar/beat arithmetic (including the negative-phase
// wrap the deployed clients already produce), the bpm rebase that keeps the
// groove from jumping, and the ping/pong offset estimator with its decaying
// RTT ceiling. Fixtures recorded off the real src/game/clock.ts.
import test from "node:test";
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import * as lib from "../test-dist/testlib.js";
const v = JSON.parse(readFileSync(new URL("./vectors/clock.json", import.meta.url), "utf8"));
test("beat / bar / step lengths match TS at every tempo", () => {
for (const c of v.math) {
assert.equal(lib.beatMsOf(c.bpm), c.beatMs, `bpm ${c.bpm}`);
assert.equal(lib.barMsOf(c.bpm), c.barMs, `bpm ${c.bpm}`);
assert.equal(lib.stepMsOf(c.bpm), c.stepMs, `bpm ${c.bpm}`);
}
});
test("the grid is 4 beats to the bar, 16 steps to the bar", () => {
assert.equal(v.beatsPerBar, 4);
assert.equal(v.stepsPerBar, 16);
assert.equal(lib.barMsOf(100) / lib.beatMsOf(100), v.beatsPerBar);
assert.equal(lib.barMsOf(100) / lib.stepMsOf(100), v.stepsPerBar);
});
test("nextBarAt, phases and beatIndex reproduce TS exactly (negatives included)", () => {
for (const c of v.math) {
const hostNow = c.now + c.offset;
const bar = lib.barMsOf(c.bpm);
const beat = lib.beatMsOf(c.bpm);
assert.equal(hostNow, c.hostNow, `hostNow bpm=${c.bpm}`);
assert.equal(lib.nextBarAt(hostNow, c.origin, bar), c.nextBarAt, `nextBarAt bpm=${c.bpm}`);
// armRecording leaves 120 ms of breathing room this side of the boundary
assert.equal(lib.nextBarAt(hostNow + 120, c.origin, bar), c.nextBarAtPlus120, `arm bpm=${c.bpm}`);
assert.equal(lib.phaseOf(hostNow, c.origin, bar), c.barPhase, `barPhase bpm=${c.bpm}`);
assert.equal(lib.phaseOf(hostNow, c.origin, beat), c.beatPhase, `beatPhase bpm=${c.bpm}`);
assert.equal(lib.beatIndexOf(hostNow, c.origin, bar, beat), c.beatIndex, `beatIndex bpm=${c.bpm}`);
assert.equal(lib.rebasedOriginOf(hostNow, c.origin, bar), c.rebasedOrigin, `rebase bpm=${c.bpm}`);
}
});
test("a rebased origin lands on a bar boundary and keeps the phase", () => {
for (const c of v.math) {
const bar = lib.barMsOf(c.bpm);
const reb = lib.rebasedOriginOf(c.hostNow, c.origin, bar);
assert.ok(reb <= c.hostNow, `bpm ${c.bpm}`);
assert.ok(c.hostNow - reb < bar, `bpm ${c.bpm}`);
}
});
test("addSyncSample: best RTT wins, worse ones are rejected, the ceiling decays", () => {
for (const s of v.sync) {
let offset = 0;
let rtt = s.rttBefore === "Infinity" ? Infinity : s.rttBefore;
let synced = s.syncedBefore;
for (const step of s.steps) {
const [o, r, y] = lib.syncSample(offset, rtt, synced, step.c, step.h, step.now);
offset = o;
rtt = r;
synced = y;
assert.equal(offset, step.offset, `${s.name} offset`);
assert.equal(rtt, step.rtt, `${s.name} rtt`);
assert.equal(synced, step.synced, `${s.name} synced`);
}
}
});
test("the RTT ceiling never runs past 2 s", () => {
const [, rtt] = lib.syncSample(0, 1990, true, 500_000, 900_000, 503_000);
assert.equal(rtt, 2000);
});
test("becoming the clock authority re-expresses the origin in my own clock", () => {
for (const c of v.authority) {
assert.equal(lib.authorityOrigin(c.originBefore, c.offsetBefore), c.origin);
assert.equal(c.offset, 0);
assert.equal(c.synced, true);
assert.equal(c.rtt, "Infinity");
}
});
test("setTransport is a plain adoption of the host's bpm and origin", () => {
assert.deepEqual(v.setTransport, { bpm: 137, origin: 42.5 });
});