refactor: 턴 경계와 12턴 묶음을 보존하는 2배속 복구 구현

This commit is contained in:
2026-09-06 10:04:33 +00:00
parent 63da0921cb
commit d3443ec7f5
53 changed files with 1467 additions and 160 deletions
@@ -93,6 +93,7 @@ export class DatabaseTurnDaemonLease {
ON CONFLICT ("profile") DO UPDATE
SET
"owner_id" = EXCLUDED."owner_id",
"clock_ready" = FALSE,
"lease_until" = EXCLUDED."lease_until",
"fencing_epoch" = CASE
WHEN "turn_daemon_lease"."owner_id" = EXCLUDED."owner_id"
@@ -126,6 +127,14 @@ export class DatabaseTurnDaemonLease {
return this.token ? { ...this.token } : null;
}
async markClockReady(): Promise<void> {
await this.db.$transaction(async (db) => {
await this.assertActive(db);
const token = this.getToken()!;
await db.turnDaemonLease.update({ where: { profile: token.profile }, data: { clockReady: true } });
});
}
isLost(): boolean {
return this.lost;
}
@@ -170,6 +170,11 @@ export class TurnDaemonLifecycle {
await this.clock.sleepMs(500);
continue;
}
if (gameClock?.mode === 'realtime' && gameClock.startsAt && wallNow < gameClock.startsAt) {
this.status.nextTurnTime = gameClock.startsAt.toISOString();
await this.clock.sleepMs(Math.min(500, gameClock.startsAt.getTime() - nowMs));
continue;
}
// 수동 실행 요청도 가오픈·정지·재조정의 턴 실행 gate를 통과해야 한다.
// 사용자 명령 처리는 루프 시작에서 계속하되 시간 진행은 여기서 분리한다.
if (this.pendingRun) {
@@ -219,7 +224,10 @@ export class TurnDaemonLifecycle {
continue;
}
const command = await this.controlQueue.waitFor(Math.max(0, nextTurnMs - gameNowMs));
const wallDeadline = await this.stateStore.projectGameDeadline?.(nextRunTime);
const command = await this.controlQueue.waitFor(
Math.max(0, wallDeadline ? wallDeadline.getTime() - nowMs : nextTurnMs - gameNowMs)
);
if (command) {
await this.handleCommand(command);
}
+2
View File
@@ -72,11 +72,13 @@ export interface TurnStateStore {
phase?: GameClockPhase;
revision?: number;
deadlineGeneration?: number;
startsAt?: Date;
}>;
promotePreopenAtOpening?(wallNow: Date): Promise<boolean>;
shouldRebaseRealtimeBacklog?(wallNow: Date): Promise<boolean>;
rebaseRealtimeBacklog?(wallNow: Date): Promise<RealtimeBacklogRebaseResult | null>;
advanceGameClockTo?(target: Date, wallNow: Date): Promise<void>;
projectGameDeadline?(gameTime: Date): Promise<Date>;
}
export interface TurnDaemonControlQueue {