fix: 재개 시 미처리 턴을 실행하고 장기 지연은 12턴씩 보정
This commit is contained in:
@@ -3,7 +3,7 @@ export const MAX_SAFE_GAME_TICK = Number.MAX_SAFE_INTEGER;
|
||||
|
||||
export type GameClockMode = 'realtime' | 'manual';
|
||||
export type GameClockPhase = 'PREOPEN' | 'RUNNING' | 'SUSPENDED' | 'RECONCILING' | 'MANUAL' | 'COMPLETED';
|
||||
export type ClockAlignmentPolicy = 'EXACT' | 'LEGACY_COMPLETE_TURNS' | 'CATCH_UP';
|
||||
export type ClockAlignmentPolicy = 'EXACT' | 'LEGACY_COMPLETE_TURNS' | 'CATCH_UP' | 'PRESERVE_SCHEDULE';
|
||||
|
||||
declare const gameTickBrand: unique symbol;
|
||||
declare const observedGameInstantBrand: unique symbol;
|
||||
@@ -103,7 +103,12 @@ export const parseGameClockPhase = (value: string): GameClockPhase => {
|
||||
throw new Error(`Unknown game clock phase: ${value}`);
|
||||
};
|
||||
|
||||
const CLOCK_ALIGNMENT_POLICIES: readonly ClockAlignmentPolicy[] = ['EXACT', 'LEGACY_COMPLETE_TURNS', 'CATCH_UP'];
|
||||
const CLOCK_ALIGNMENT_POLICIES: readonly ClockAlignmentPolicy[] = [
|
||||
'EXACT',
|
||||
'LEGACY_COMPLETE_TURNS',
|
||||
'CATCH_UP',
|
||||
'PRESERVE_SCHEDULE',
|
||||
];
|
||||
|
||||
export const parseClockAlignmentPolicy = (value: string): ClockAlignmentPolicy => {
|
||||
if ((CLOCK_ALIGNMENT_POLICIES as readonly string[]).includes(value)) {
|
||||
@@ -188,6 +193,15 @@ export const buildClockAlignmentPlan = (input: {
|
||||
ticksPerSecond: number;
|
||||
catchUpTicks?: number;
|
||||
}): ClockAlignmentPlan => {
|
||||
if (input.policy === 'PRESERVE_SCHEDULE') {
|
||||
if ((input.catchUpTicks ?? 0) !== 0) {
|
||||
throw new Error('PRESERVE_SCHEDULE derives catch-up from the complete wall gap.');
|
||||
}
|
||||
const exact = buildAlignmentPlan({ ...input, catchUpTicks: 0 });
|
||||
// 운영 재개는 관측 시계만 현재로 돌린다. 예약/실행 커서는 보존하고
|
||||
// 정상 엔진이 따라잡으며, 12턴 묶음 생략은 backlog 처리 한 곳에서 결정한다.
|
||||
return { ...exact, shiftTicks: asGameTick(0), catchUpTicks: exact.gapTicks };
|
||||
}
|
||||
if (input.policy === 'EXACT') {
|
||||
if ((input.catchUpTicks ?? 0) !== 0) {
|
||||
throw new Error('EXACT alignment does not allow catch-up ticks.');
|
||||
|
||||
@@ -160,6 +160,28 @@ describe('GameClock', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it.each([0, 3_142_625, 6 * 3_600_000 + 3_142_625, 13 * 3_600_000])(
|
||||
'resumes observation after %i ms without moving any schedule',
|
||||
(gapMs) => {
|
||||
const cutWall = new Date('2026-09-06T05:48:07.986Z');
|
||||
const plan = buildClockAlignmentPlan({
|
||||
policy: 'PRESERVE_SCHEDULE',
|
||||
sourceRevision: 2,
|
||||
cutTick: 123,
|
||||
cutWall,
|
||||
resumeWall: new Date(cutWall.getTime() + gapMs),
|
||||
ticksPerSecond: 10_000,
|
||||
});
|
||||
expect(plan).toMatchObject({
|
||||
shiftTicks: 0,
|
||||
catchUpTicks: gapMs * 10,
|
||||
alignedTick: 123 + gapMs * 10,
|
||||
sourceRevision: 2,
|
||||
targetRevision: 3,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('preserves schedule ordering, remaining distance, and occurrence ticks across generated exact gaps', () => {
|
||||
let seed = 0x5eed1234;
|
||||
const next = (): number => {
|
||||
|
||||
Reference in New Issue
Block a user