fix(engine): 서버 재개 시 게임 시계를 Ref 기준으로 보정한다
짧은 중단은 순서대로 따라잡고 장기 중단은 턴 간격별 Ref 한도를 넘어선 완전 턴을 일괄 이동한다. 장수와 미완료 경매 시각을 같은 트랜잭션에서 저장하고 표시 시각 지연을 복구한다.
This commit is contained in:
@@ -171,7 +171,8 @@ export class TurnDaemonLifecycle {
|
||||
}
|
||||
|
||||
const nowMs = this.clock.nowMs();
|
||||
const gameClock = await this.stateStore.loadGameClock?.(new Date(nowMs));
|
||||
const wallNow = new Date(nowMs);
|
||||
const gameClock = await this.stateStore.loadGameClock?.(wallNow);
|
||||
if (gameClock?.mode === 'manual') {
|
||||
// Ref observes all generals due before one monthly boundary in
|
||||
// a single snapshot. Manual mode advances directly to that
|
||||
@@ -186,6 +187,13 @@ export class TurnDaemonLifecycle {
|
||||
await this.runOnce({ reason: 'schedule', targetTime });
|
||||
continue;
|
||||
}
|
||||
if (
|
||||
gameClock?.mode === 'realtime' &&
|
||||
(await this.stateStore.shouldRebaseRealtimeBacklog?.(wallNow)) &&
|
||||
(await this.rebaseRealtimeBacklog(wallNow))
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const gameNowMs = gameClock?.now.getTime() ?? nowMs;
|
||||
const nextTurnMs = nextRunTime.getTime();
|
||||
if (gameNowMs >= nextTurnMs) {
|
||||
@@ -419,6 +427,58 @@ export class TurnDaemonLifecycle {
|
||||
}
|
||||
}
|
||||
|
||||
private async rebaseRealtimeBacklog(wallNow: Date): Promise<boolean> {
|
||||
if (!this.stateStore.rebaseRealtimeBacklog) {
|
||||
return false;
|
||||
}
|
||||
const startMs = this.clock.nowMs();
|
||||
let result: TurnRunResult | null;
|
||||
try {
|
||||
const rebaseAndFlush = async (): Promise<TurnRunResult | null> => {
|
||||
const rebased = await this.stateStore.rebaseRealtimeBacklog!(wallNow);
|
||||
if (!rebased) {
|
||||
return null;
|
||||
}
|
||||
const nextResult: TurnRunResult = {
|
||||
lastTurnTime: rebased.lastTurnTime,
|
||||
processedGenerals: 0,
|
||||
processedTurns: 0,
|
||||
durationMs: 0,
|
||||
partial: false,
|
||||
checkpoint: rebased.checkpoint,
|
||||
};
|
||||
this.status.state = 'flushing';
|
||||
await this.hooks?.flushChanges?.(nextResult);
|
||||
return nextResult;
|
||||
};
|
||||
result = this.stateManager ? await this.stateManager.transaction(rebaseAndFlush) : await rebaseAndFlush();
|
||||
} catch (error) {
|
||||
this.status.running = false;
|
||||
this.status.state = 'paused';
|
||||
this.status.paused = true;
|
||||
this.errorPaused = true;
|
||||
this.status.lastError = error instanceof Error ? error.message : 'Unknown realtime backlog rebase error.';
|
||||
await this.hooks?.onRunError?.(error);
|
||||
// The rebase attempt was handled, albeit as a pause. Do not fall
|
||||
// through and execute overdue turns against a transaction that
|
||||
// failed to persist its clock/schedule shift.
|
||||
return true;
|
||||
}
|
||||
if (!result) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.applyRunResult(result, startMs);
|
||||
this.status.state = 'idle';
|
||||
try {
|
||||
await this.hooks?.publishEvents?.(result);
|
||||
} catch (error) {
|
||||
this.status.lastError =
|
||||
error instanceof Error ? error.message : 'Unknown backlog rebase event publication error.';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private async applyRunResult(result: TurnRunResult, startMs: number): Promise<void> {
|
||||
this.status.lastRunAt = new Date(startMs).toISOString();
|
||||
this.status.lastDurationMs = Math.max(0, this.clock.nowMs() - startMs);
|
||||
|
||||
Reference in New Issue
Block a user