Fix active and match timer drift between status polls

Persist occupancy transitions with telemetry and carry timer anchors through live updates. Cover delayed polling, lobby rejoining, and lifecycle boundaries with regression tests.

Fixes #47
This commit is contained in:
2026-09-27 23:29:04 -04:00
parent b904ddbc48
commit 2fa3792482
8 changed files with 210 additions and 15 deletions
+24
View File
@@ -13,6 +13,30 @@ afterEach(async () => fixture.cleanup());
const payload = gameEvent('round_change', 17);
it('does not let a request received before a lifecycle submission restore cleared timers', async () => {
const { db } = await import('../../src/lib/server/db/index.js');
const { saveTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
const { getServer } = await import('../../src/lib/server/fleet/servers.js');
const at = (seconds: number) => new Date(Date.parse('2026-09-20T12:00:00Z') + seconds * 1000).toISOString();
db().prepare('INSERT INTO restart_rollout (server, requested_at) VALUES (?, ?)').run(payload.server, at(10));
for (const seconds of [9, 10]) {
saveTelemetry({ ...gameEvent('game_start'), receivedAt: at(seconds) });
expect(getServer(payload.server)).toMatchObject({ active_since: null, game_started_at: null });
}
const current = saveTelemetry({ ...gameEvent('heartbeat', 2), receivedAt: at(11) });
expect(current.timing?.server).toEqual({ activeSince: at(11), inactiveSince: null, gameStartedAt: null, gameEndedAt: null });
});
it('generates timer anchors on the server instead of accepting client-supplied timing', async () => {
const response = await post({ ...gameEvent('game_start'), receivedAt: '2000-01-01T00:00:00Z',
timing: { server: { activeSince: '2000-01-01T00:00:00Z' } } });
const { telemetry } = await response.json();
expect(response.status).toBe(200);
expect(telemetry.receivedAt).not.toBe('2000-01-01T00:00:00Z');
expect(telemetry.timing.server).toEqual({ activeSince: telemetry.receivedAt, inactiveSince: null,
gameStartedAt: telemetry.receivedAt, gameEndedAt: null });
});
it('persists player timing across matches and resets after absence or a telemetry gap', async () => {
const { saveTelemetry, listTelemetry } = await import('../../src/lib/server/fleet/telemetry.js');
const { closeDb } = await import('../../src/lib/server/db/index.js');