feat: 메인 현재 시각을 최근 통신 기준으로 갱신

완료 턴 시각 대신 lobby serverTime을 분 경계에 맞춰 표시한다. 게임 API 응답과 SSE 및 같은 계정 탭 전달을 최근 통신으로 기록하고, 45초 공백에는 시계를 멈춘다. 공통 시계 투영과 단위 및 Chromium 회귀 검증을 추가한다.
This commit is contained in:
2026-08-21 17:19:46 +00:00
parent b82492e6d1
commit 1e14320911
10 changed files with 374 additions and 35 deletions
@@ -0,0 +1,22 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
GAME_SERVER_ACTIVITY_FRESHNESS_MS,
createGameServerActivityTracker,
isRecentGameServerActivity,
} from '../src/utils/gameServerActivity.ts';
void test('keeps the most recently observed server contact timestamp', () => {
const tracker = createGameServerActivityTracker();
tracker.markContact(2_000);
tracker.markContact(1_000);
tracker.markContact(Number.NaN);
assert.equal(tracker.lastContactAt.value, 1_000);
});
void test('treats three heartbeat intervals as recent activity', () => {
assert.equal(isRecentGameServerActivity(1_000, 1_000 + GAME_SERVER_ACTIVITY_FRESHNESS_MS), true);
assert.equal(isRecentGameServerActivity(1_000, 1_001 + GAME_SERVER_ACTIVITY_FRESHNESS_MS), false);
assert.equal(isRecentGameServerActivity(null, 1_000), false);
});
@@ -0,0 +1,51 @@
import assert from 'node:assert/strict';
import test from 'node:test';
import {
millisecondsUntilNextMinute,
projectServerClock,
sampleServerClock,
} from '../src/utils/serverClockProjection.ts';
void test('projects a running server clock from the browser sample instant', () => {
const sample = sampleServerClock(
{
serverTime: '2026-08-13T00:00:35.250Z',
clockMode: 'realtime',
clockRunning: true,
},
10_000
);
assert.ok(sample);
assert.equal(projectServerClock(sample, 34_750).time.toISOString(), '2026-08-13T00:01:00.000Z');
assert.equal(millisecondsUntilNextMinute(projectServerClock(sample, 10_000).time), 24_750);
});
void test('keeps manual clocks fixed even while client time advances', () => {
const sample = sampleServerClock(
{ serverTime: '2026-08-13T00:00:35.000Z', clockMode: 'manual', clockRunning: false },
10_000
);
assert.ok(sample);
assert.equal(projectServerClock(sample, 130_000).time.toISOString(), '2026-08-13T00:00:35.000Z');
});
void test('holds a preopen clock until its wall-clock start delay passes', () => {
const sample = sampleServerClock(
{
serverTime: '2026-08-13T00:00:00.000Z',
serverWallTime: '2026-08-13T08:00:00.000Z',
clockMode: 'realtime',
clockRunning: false,
clockStartsAt: '2026-08-13T08:01:00.000Z',
},
10_000
);
assert.ok(sample);
assert.equal(projectServerClock(sample, 69_999).time.toISOString(), '2026-08-13T00:00:00.000Z');
assert.equal(projectServerClock(sample, 70_001).time.toISOString(), '2026-08-13T00:00:00.001Z');
});
void test('rejects an invalid server clock sample', () => {
assert.equal(sampleServerClock({ serverTime: 'not-a-time' }, 10_000), null);
});