diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index 2b8476e..14f0581 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -1283,14 +1283,21 @@ export const createReservedTurnHandler = async (options: { } } - const hasNationChange = (resolution.patches?.cities ?? []).some((patch) => { + const cityNationChanges = (resolution.patches?.cities ?? []).flatMap((patch) => { if (!Object.prototype.hasOwnProperty.call(patch.patch ?? {}, 'nationId')) { - return false; + return []; } const nextNationId = patch.patch.nationId; const previousNationId = cityNationIdsBeforeExecution.get(patch.id); - return previousNationId === undefined || nextNationId !== previousNationId; + if ( + typeof nextNationId !== 'number' || + (previousNationId !== undefined && nextNationId === previousNationId) + ) { + return []; + } + return [{ cityId: patch.id, nextNationId }]; }); + const hasNationChange = cityNationChanges.length > 0; const refreshesFrontForDiplomacyState = actionKey === 'che_이호경식' && resolution.effects.some( @@ -1309,9 +1316,32 @@ export const createReservedTurnHandler = async (options: { if ((hasNationChange || refreshesFrontForDiplomacyState) && !preservesImmediateFrontState) { const worldView = worldOverlay?.view ?? worldRef; if (worldView && options.map) { + const frontNationIds = new Set(); + const cityById = new Map(worldView.listCities().map((city) => [city.id, city] as const)); + const mapCityById = new Map(options.map.cities.map((city) => [city.id, city] as const)); + for (const change of cityNationChanges) { + frontNationIds.add(change.nextNationId); + const mapCity = mapCityById.get(change.cityId); + for (const cityId of [change.cityId, ...(mapCity?.connections ?? [])]) { + const nationId = cityById.get(cityId)?.nationId; + if (nationId && nationId > 0) { + frontNationIds.add(nationId); + } + } + } + if (refreshesFrontForDiplomacyState) { + for (const effect of resolution.effects) { + if (effect.type !== 'diplomacy:patch') { + continue; + } + frontNationIds.add(effect.srcNationId); + frontNationIds.add(effect.destNationId); + } + } const frontPatches = buildFrontStatePatches({ worldView, map: options.map, + nationIds: [...frontNationIds], }); if (frontPatches.length > 0) { for (const patch of frontPatches) { diff --git a/tools/integration-tests/test/turnCommandCoreReference.integration.test.ts b/tools/integration-tests/test/turnCommandCoreReference.integration.test.ts index a9b69de..5ccca51 100644 --- a/tools/integration-tests/test/turnCommandCoreReference.integration.test.ts +++ b/tools/integration-tests/test/turnCommandCoreReference.integration.test.ts @@ -82,6 +82,7 @@ integration('core ↔ legacy command-boundary differential', () => { 'fixtures/turn-differential/live-sortie-multiple-defenders.json', ], ['live sortie supply retreat', 'fixtures/turn-differential/live-sortie-supply-retreat.json'], + ['live sortie noncapital conquest', 'fixtures/turn-differential/live-sortie-noncapital-conquest.json'], ])( '%s matches command RNG and canonical state delta', async (_label, fixturePath) => { @@ -113,6 +114,12 @@ integration('core ↔ legacy command-boundary differential', () => { expect(retreatLogs.some((log) => String(log.text).includes('병량 부족'))).toBe(true); expect(reference.after.cities.find((city) => city.id === 70)?.nationId).toBe(1); } + if (fixturePath.endsWith('live-sortie-noncapital-conquest.json')) { + expect(reference.after.cities.find((city) => city.id === 70)?.nationId).toBe(1); + expect(reference.after.cities.find((city) => city.id === 71)?.nationId).toBe(2); + expect(reference.after.nations.some((nation) => nation.id === 2)).toBe(true); + expect(reference.after.generals.find((general) => general.id === 2)?.nationId).toBe(2); + } expect(core.execution.outcome).toMatchObject({ requestedAction: request.action,