fix(game): preserve scenario NPC progression

This commit is contained in:
2026-08-02 08:20:42 +00:00
parent e3c5d03cbe
commit 791f6e1395
7 changed files with 227 additions and 29 deletions
+13 -6
View File
@@ -553,6 +553,7 @@ export const createDatabaseTurnHooks = async (
profileName?: string;
reservedTurns?: InMemoryReservedTurnStore;
turnDaemonLease?: DatabaseTurnDaemonLease;
transactionTimeoutMs?: number;
}
): Promise<DatabaseTurnHooks> => {
// 턴 처리 결과를 DB에 반영하는 훅을 만든다.
@@ -999,7 +1000,10 @@ export const createDatabaseTurnHooks = async (
if (transaction) {
await persist(transaction);
} else {
await prisma.$transaction(persist);
await prisma.$transaction(
persist,
options?.transactionTimeoutMs ? { timeout: options.transactionTimeoutMs } : undefined
);
}
return () => {
@@ -1020,11 +1024,14 @@ export const createDatabaseTurnHooks = async (
acknowledge();
},
executeCommand: async (requestId, execute) => {
const committed = await prisma.$transaction(async (transaction) => {
const result = await execute({ db: transaction });
const acknowledge = await persistChanges(transaction, { requestId, result });
return { result, acknowledge };
});
const committed = await prisma.$transaction(
async (transaction) => {
const result = await execute({ db: transaction });
const acknowledge = await persistChanges(transaction, { requestId, result });
return { result, acknowledge };
},
options?.transactionTimeoutMs ? { timeout: options.transactionTimeoutMs } : undefined
);
committed.acknowledge();
return committed.result;
},
+22
View File
@@ -102,6 +102,13 @@ export interface TurnDaemonRuntimeOptions {
leaseDurationMs?: number;
leaseOwnerId?: string;
enableLeaseHeartbeat?: boolean;
/**
* Isolated, single-process fixture acceleration only. Reserved turns are
* loaded once and no concurrent API writer may touch this database.
*/
exclusiveFastForward?: boolean;
databaseTransactionTimeoutMs?: number;
onActionResolved?: NonNullable<Parameters<typeof createReservedTurnHandler>[0]['onActionResolved']>;
}
export interface TurnDaemonRuntime {
@@ -179,6 +186,9 @@ const createTurnDaemonRuntimeWithLease = async (
databaseFlushEnabled: boolean,
turnDaemonLease: DatabaseTurnDaemonLease | null
): Promise<TurnDaemonRuntime> => {
if (options.exclusiveFastForward && options.profileName) {
throw new Error('exclusiveFastForward cannot be used with a gateway-managed profile.');
}
// DB에서 월드를 읽고 턴 데몬을 구동할 런타임을 만든다.
const { state, snapshot } = await loadTurnWorldFromDatabase({
databaseUrl: options.databaseUrl,
@@ -499,6 +509,7 @@ const createTurnDaemonRuntimeWithLease = async (
commandProfile,
commandEnv: monthlyCommandEnv,
getAdditionalOccupiedUniqueItemKeys: () => occupiedAuctionUniqueItemKeys,
onActionResolved: options.onActionResolved,
})),
calendarHandler: calendarHandler ?? undefined,
autoAdvanceDiplomacyMonth: false,
@@ -538,10 +549,20 @@ const createTurnDaemonRuntimeWithLease = async (
});
const stateStore = new InMemoryTurnStateStore(world);
let fastForwardPreparedMonth = '';
const processor = new InMemoryTurnProcessor(world, {
tickMinutes,
beforeExecuteGeneral: reservedTurnStoreHandle
? async (general) => {
if (options.exclusiveFastForward) {
const state = world.getState();
const monthKey = `${state.currentYear}-${state.currentMonth}`;
if (fastForwardPreparedMonth !== monthKey) {
await refreshOccupiedAuctionUniqueItemKeys();
fastForwardPreparedMonth = monthKey;
}
return;
}
const promises: Promise<unknown>[] = [];
promises.push(
reservedTurnStoreHandle.store.prepareTurnsForExecution(
@@ -597,6 +618,7 @@ const createTurnDaemonRuntimeWithLease = async (
profileName: options.profileName ?? options.profile,
reservedTurns: reservedTurnStoreHandle?.store,
turnDaemonLease: turnDaemonLease ?? undefined,
transactionTimeoutMs: options.databaseTransactionTimeoutMs,
});
auctionBidder = await createAuctionBidder({
databaseUrl: options.databaseUrl,