fix: 통일 완료 뒤 턴 실행을 중단

Ref처럼 isunited 2/3에서 예약 스케줄을 멈추고 processor 직접 호출도 no-op 처리한다. 이미 기본값으로 내려간 종료 게임의 refreshLimit만 식별해 복구하는 마이그레이션과 회귀 테스트를 추가한다.
This commit is contained in:
2026-08-20 06:13:28 +00:00
parent 107b433d85
commit 92ad49db45
9 changed files with 83 additions and 2 deletions
@@ -14,6 +14,40 @@ import {
const addMinutes = (time: Date, minutes: number): Date => new Date(time.getTime() + minutes * 60_000);
describe('TurnDaemonLifecycle', () => {
it('does not schedule another processor run after the world reaches a terminal united state', async () => {
const clock = new ManualClock(new Date('2026-01-01T00:00:00.000Z').getTime());
const controlQueue = new InMemoryControlQueue();
const processor = { run: vi.fn() };
const lifecycle = new TurnDaemonLifecycle(
{
clock,
controlQueue,
getNextTickTime: (value) => addMinutes(value, 60),
stateStore: {
loadLastTurnTime: async () => new Date('2042-01-01T00:00:00.000Z'),
loadNextGeneralTurnTime: async () => new Date('2042-01-01T00:30:00.000Z'),
saveLastTurnTime: async () => {},
loadCheckpoint: async () => undefined,
saveCheckpoint: async () => {},
shouldHaltScheduledRuns: async () => {
controlQueue.enqueue({ type: 'shutdown', reason: 'terminal world verified' });
return true;
},
},
processor,
},
{
profile: 'terminal-united',
defaultBudget: { budgetMs: 100, maxGenerals: 10, catchUpCap: 1 },
}
);
await lifecycle.start();
expect(processor.run).not.toHaveBeenCalled();
expect(lifecycle.getStatus().nextTurnTime).toBeUndefined();
});
it('runs manual game time to each monthly snapshot without waiting for wall time', async () => {
const wallNow = new Date('2026-01-01T00:00:00.000Z');
const operationalClock = new ManualClock(wallNow.getTime());
+9
View File
@@ -286,5 +286,14 @@ describe('InMemoryTurnProcessor ordering', () => {
expect(result.processedTurns).toBe(1);
expect(world.getState()).toMatchObject({ currentYear: 189, currentMonth: 2, meta: { isUnited: 2 } });
world.updateWorldMeta({ refreshLimit: 12_000 });
const haltedResult = await processor.run(addMinutes(baseTime, 60), {
budgetMs: 1_000,
maxGenerals: 10,
catchUpCap: 10,
});
expect(haltedResult).toMatchObject({ processedGenerals: 0, processedTurns: 0 });
expect(world.getState().meta).toMatchObject({ isUnited: 2, refreshLimit: 12_000 });
});
});