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,67 @@
export type ServerClockProjectionInput = {
serverTime?: string;
serverWallTime?: string;
clockMode?: 'realtime' | 'manual';
clockRunning?: boolean;
clockStartsAt?: string | null;
};
export type SampledServerClock = {
serverTimeMs: number;
sampledClientTimeMs: number;
clockMode: 'realtime' | 'manual';
startDelayMs: number | null;
};
const parseInstant = (value?: string | null): number | null => {
if (!value) return null;
const parsed = new Date(value).getTime();
return Number.isFinite(parsed) ? parsed : null;
};
export const sampleServerClock = (
input: ServerClockProjectionInput,
sampledClientTimeMs = Date.now()
): SampledServerClock | null => {
const serverTimeMs = parseInstant(input.serverTime);
if (serverTimeMs === null) return null;
let startDelayMs: number | null;
if (input.clockMode === 'manual') {
startDelayMs = null;
} else if (input.clockRunning !== false) {
startDelayMs = 0;
} else {
const serverWallTimeMs = parseInstant(input.serverWallTime);
const clockStartsAtMs = parseInstant(input.clockStartsAt);
startDelayMs =
serverWallTimeMs !== null && clockStartsAtMs !== null
? Math.max(0, clockStartsAtMs - serverWallTimeMs)
: null;
}
return {
serverTimeMs,
sampledClientTimeMs,
clockMode: input.clockMode ?? 'realtime',
startDelayMs,
};
};
export const projectServerClock = (sample: SampledServerClock, clientTimeMs = Date.now()) => {
const clientElapsedMs = Math.max(0, clientTimeMs - sample.sampledClientTimeMs);
const elapsedGameMs =
sample.clockMode === 'manual' || sample.startDelayMs === null
? 0
: Math.max(0, clientElapsedMs - sample.startDelayMs);
return {
clientElapsedMs,
time: new Date(sample.serverTimeMs + elapsedGameMs),
};
};
export const millisecondsUntilNextMinute = (time: Date): number => {
const remainder = ((time.getTime() % 60_000) + 60_000) % 60_000;
return remainder === 0 ? 60_000 : 60_000 - remainder;
};