fix: Ref 게임 로직과 시나리오 풀 호환을 보정

월 경계, 전투 기술 상한, 연감과 베팅·설문·경매 정산 순서를 Ref 계약에 맞춘다.\n\n시나리오 일반 풀을 ENGINE mutation과 logical tick 기반으로 직렬화하고 914·915 catalog 및 조건부 100기 pool 실행 경계를 추가한다.\n\n경매 worker는 세대별 durable event만 만들고 ENGINE이 row lock 후 상태 전이와 정산을 단일 transaction으로 소유한다.
This commit is contained in:
2026-08-23 16:26:14 +00:00
parent bf6b7be7b0
commit 85591c68ad
114 changed files with 13327 additions and 901 deletions
@@ -241,6 +241,49 @@ describe('TurnDaemonLifecycle', () => {
expect(observedTargets[0]?.toISOString()).toBe('2042-01-01T02:59:59.999Z');
});
it('limits an explicit manual run target to the next monthly boundary', async () => {
const lastTurnTime = new Date('2042-01-01T00:00:00.000Z');
const requestedTarget = addMinutes(lastTurnTime, 180);
const queue = new InMemoryControlQueue();
const processor: TurnProcessor = {
run: vi.fn(async (target): Promise<TurnRunResult> => {
queue.enqueue({ type: 'shutdown', reason: 'verified' });
return {
lastTurnTime: target.toISOString(),
processedGenerals: 1,
processedTurns: 1,
durationMs: 0,
partial: false,
};
}),
};
const lifecycle = new TurnDaemonLifecycle(
{
clock: new ManualClock(lastTurnTime.getTime()),
controlQueue: queue,
getNextTickTime: (value) => addMinutes(value, 60),
stateStore: {
loadLastTurnTime: async () => lastTurnTime,
loadNextGeneralTurnTime: async () => addMinutes(lastTurnTime, 30),
saveLastTurnTime: async () => {},
loadCheckpoint: async () => undefined,
saveCheckpoint: async () => {},
},
processor,
},
{
profile: 'manual-explicit-boundary',
defaultBudget: { budgetMs: 100, maxGenerals: 10, catchUpCap: 1 },
}
);
lifecycle.requestRun('manual', requestedTarget);
await lifecycle.start();
expect(processor.run).toHaveBeenCalledOnce();
expect((processor.run as ReturnType<typeof vi.fn>).mock.calls[0]?.[0]).toEqual(addMinutes(lastTurnTime, 60));
});
it('produces the same command, RNG, and resource state in realtime and manual modes', async () => {
const start = new Date('2042-01-01T00:00:00.000Z');
const runMode = async (mode: 'realtime' | 'manual') => {