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
+30 -1
View File
@@ -17,11 +17,13 @@ import type {
GeneralLastTurn,
Nation,
ScenarioConfig,
ScenarioGeneralPoolCandidate,
ScenarioMeta,
Troop,
TriggerValue,
} from '@sammo-ts/logic';
import { normalizeScenarioEffect } from '@sammo-ts/logic';
import { parseScenarioGeneralPoolCandidate } from '@sammo-ts/logic';
import { projectItemSlots, readItemInventoryFromMeta } from '@sammo-ts/logic/items/index.js';
import { z } from 'zod';
import { GameClock, asRecord, isRecord, type GameClockMode } from '@sammo-ts/common';
@@ -30,7 +32,7 @@ import type { MapLoaderOptions } from '../scenario/mapLoader.js';
import { loadMapDefinitionByName } from '../scenario/mapLoader.js';
import type { UnitSetLoaderOptions } from '../scenario/unitSetLoader.js';
import { loadUnitSetDefinitionByName } from '../scenario/unitSetLoader.js';
import type { TurnDiplomacy, TurnEvent, TurnGeneral, TurnWorldLoadResult } from './types.js';
import type { TurnDiplomacy, TurnEvent, TurnGeneral, TurnGeneralPoolEntry, TurnWorldLoadResult } from './types.js';
import { readDiplomacyMeta } from '@sammo-ts/logic';
import { applyPersistedRankRowsToMeta } from './rankData.js';
@@ -360,6 +362,27 @@ const mapEventRow = (row: {
meta: asRecord(row.meta),
});
const mapGeneralPoolRow = (row: {
id: number;
uniqueName: string;
ownerUserId: string | null;
generalId: number | null;
reservedUntil: Date | null;
reservedUntilTick: bigint | null;
info: JsonValue;
}): TurnGeneralPoolEntry => ({
id: row.id,
uniqueName: row.uniqueName,
ownerUserId: row.ownerUserId,
generalId: row.generalId,
reservedUntil: row.reservedUntil,
reservedUntilTick:
row.reservedUntilTick === null
? null
: toSafeTick(row.reservedUntilTick, `select_pool.reserved_until_tick(${row.id})`),
candidate: parseScenarioGeneralPoolCandidate(row) satisfies ScenarioGeneralPoolCandidate,
});
const mapTroopRow = (row: TurnEngineTroopRow): Troop => ({
id: row.troopLeaderId,
nationId: row.nationId,
@@ -386,6 +409,7 @@ export const loadTurnWorldFromDatabase = async (options: TurnWorldLoaderOptions)
diplomacyRows,
troopRows,
eventRows,
generalPoolRows,
] = await Promise.all([
prisma.general.findMany(),
prisma.rankData.findMany(),
@@ -398,6 +422,7 @@ export const loadTurnWorldFromDatabase = async (options: TurnWorldLoaderOptions)
prisma.event.findMany({
orderBy: [{ priority: 'desc' }, { id: 'asc' }],
}),
prisma.selectPoolEntry.findMany({ orderBy: { id: 'asc' } }),
]);
const meta = asRecord(worldState.meta);
@@ -465,6 +490,7 @@ export const loadTurnWorldFromDatabase = async (options: TurnWorldLoaderOptions)
const worldConfig = asRecord(worldState.config);
const scenarioConfig = mapScenarioConfig(worldState.config);
const targetGeneralPool = scenarioConfig.map.targetGeneralPool;
const mapName = scenarioConfig.environment?.mapName ?? 'che';
const map = await loadMapDefinitionByName(mapName, options.mapOptions);
const unitSetName = scenarioConfig.environment?.unitSet ?? 'che';
@@ -508,6 +534,9 @@ export const loadTurnWorldFromDatabase = async (options: TurnWorldLoaderOptions)
diplomacy,
events,
initialEvents,
...(typeof targetGeneralPool === 'string'
? { generalPoolEntries: generalPoolRows.map(mapGeneralPoolRow) }
: {}),
},
};
} finally {