feat: 게임 시계 reconciliation 권위 기반 추가
This commit is contained in:
@@ -1,4 +1,10 @@
|
||||
import { GameClock, type GameClockMode } from '@sammo-ts/common';
|
||||
import {
|
||||
GameClock,
|
||||
inferClockPhase,
|
||||
parseGameClockPhase,
|
||||
type GameClockMode,
|
||||
type GameClockPhase,
|
||||
} from '@sammo-ts/common';
|
||||
|
||||
import type { DatabaseClient } from '../context.js';
|
||||
|
||||
@@ -7,6 +13,9 @@ export interface CurrentGameTime {
|
||||
wallNow: Date;
|
||||
tick: number | null;
|
||||
mode: GameClockMode | null;
|
||||
phase?: GameClockPhase | null;
|
||||
revision?: number | null;
|
||||
deadlineGeneration?: number | null;
|
||||
running: boolean;
|
||||
startsAt: Date | null;
|
||||
dateToTick(date: Date): number | null;
|
||||
@@ -19,6 +28,9 @@ export const loadCurrentGameTime = async (db: DatabaseClient, wallNow = new Date
|
||||
wallNow,
|
||||
tick: null,
|
||||
mode: null,
|
||||
phase: null,
|
||||
revision: null,
|
||||
deadlineGeneration: null,
|
||||
running: true,
|
||||
startsAt: null,
|
||||
dateToTick: () => null,
|
||||
@@ -32,6 +44,9 @@ export const loadCurrentGameTime = async (db: DatabaseClient, wallNow = new Date
|
||||
clockMode: true,
|
||||
clockWallAnchor: true,
|
||||
tickSeconds: true,
|
||||
clockPhase: true,
|
||||
clockRevision: true,
|
||||
deadlineGeneration: true,
|
||||
},
|
||||
});
|
||||
if (!state?.clockBaseTime || state.clockTick === null || !state.clockWallAnchor) {
|
||||
@@ -40,32 +55,53 @@ export const loadCurrentGameTime = async (db: DatabaseClient, wallNow = new Date
|
||||
wallNow,
|
||||
tick: null,
|
||||
mode: null,
|
||||
phase: null,
|
||||
revision: null,
|
||||
deadlineGeneration: null,
|
||||
running: true,
|
||||
startsAt: null,
|
||||
dateToTick: () => null,
|
||||
};
|
||||
}
|
||||
const mode: GameClockMode = state.clockMode === 'manual' ? 'manual' : 'realtime';
|
||||
// During the dual-read migration an older profile (or a rolling-deploy
|
||||
// fixture) can lack clock_phase. Preserve the existing future-anchor
|
||||
// PREOPEN contract until every profile has the authoritative column.
|
||||
const phase = state.clockPhase
|
||||
? parseGameClockPhase(state.clockPhase)
|
||||
: mode === 'realtime' && wallNow.getTime() < state.clockWallAnchor.getTime()
|
||||
? 'PREOPEN'
|
||||
: inferClockPhase(mode);
|
||||
const storedTick = Number(state.clockTick);
|
||||
if (!Number.isSafeInteger(storedTick)) {
|
||||
throw new Error(`world_state.clock_tick is outside the JavaScript safe integer range: ${state.clockTick}`);
|
||||
}
|
||||
const revision = Number(state.clockRevision ?? 1n);
|
||||
const deadlineGeneration = Number(state.deadlineGeneration ?? 1n);
|
||||
if (!Number.isSafeInteger(revision) || !Number.isSafeInteger(deadlineGeneration)) {
|
||||
throw new Error('world_state clock revision or deadline generation is outside the safe integer range.');
|
||||
}
|
||||
const clock = new GameClock({
|
||||
baseTime: state.clockBaseTime,
|
||||
tick: storedTick,
|
||||
mode,
|
||||
wallAnchor: state.clockWallAnchor,
|
||||
turnSeconds: state.tickSeconds,
|
||||
phase,
|
||||
revision,
|
||||
});
|
||||
const tick = clock.nowTick(wallNow);
|
||||
const running = mode === 'realtime' && wallNow.getTime() >= state.clockWallAnchor.getTime();
|
||||
const running = phase === 'RUNNING' && mode === 'realtime';
|
||||
return {
|
||||
now: clock.tickToDate(tick),
|
||||
wallNow,
|
||||
tick,
|
||||
mode,
|
||||
phase,
|
||||
revision,
|
||||
deadlineGeneration,
|
||||
running,
|
||||
startsAt: mode === 'realtime' && !running ? state.clockWallAnchor : null,
|
||||
startsAt: phase === 'PREOPEN' ? state.clockWallAnchor : null,
|
||||
dateToTick: (date) => clock.dateToTick(date),
|
||||
};
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user