diff --git a/app/game-engine/src/turn/reservedTurnHandler.ts b/app/game-engine/src/turn/reservedTurnHandler.ts index c9c09f6..eb80929 100644 --- a/app/game-engine/src/turn/reservedTurnHandler.ts +++ b/app/game-engine/src/turn/reservedTurnHandler.ts @@ -1159,22 +1159,32 @@ export const createReservedTurnHandler = async (options: { ) ); currentCity = currentCity ? { ...currentCity, meta: { ...currentCity.meta } } : currentCity; - const cityGeneralCopies = new Map(); - for (const general of worldView?.listGenerals() ?? []) { - cityGeneralCopies.set( - general.id, - general.id === currentGeneral.id ? currentGeneral : cloneTurnGeneral(general) - ); - } - cityGeneralCopies.set(currentGeneral.id, currentGeneral); + let cityGeneralCopies: Map | undefined; + const getCityGeneralCopies = (): Map => { + if (cityGeneralCopies) { + return cityGeneralCopies; + } + cityGeneralCopies = new Map(); + for (const general of worldView?.listGenerals() ?? []) { + if (general.cityId !== currentGeneral.cityId) { + continue; + } + cityGeneralCopies.set( + general.id, + general.id === currentGeneral.id ? currentGeneral : cloneTurnGeneral(general) + ); + } + cityGeneralCopies.set(currentGeneral.id, currentGeneral); + return cityGeneralCopies; + }; const preTurnPipeline = new GeneralActionPipeline(env.generalActionModules ?? []); const preTurnContext = createGeneralTriggerContext({ general: currentGeneral, nation: currentNation, worldView: { - listGenerals: () => Array.from(cityGeneralCopies.values()), + listGenerals: () => Array.from(getCityGeneralCopies().values()), listGeneralsByCity: (cityId) => - Array.from(cityGeneralCopies.values()).filter((general) => general.cityId === cityId), + Array.from(getCityGeneralCopies().values()).filter((general) => general.cityId === cityId), }, rng: preprocessRng, log: { @@ -1209,7 +1219,7 @@ export const createReservedTurnHandler = async (options: { } preTurnContext.skill.activate('pre.병력군량소모'); } - for (const [generalId, next] of cityGeneralCopies) { + for (const [generalId, next] of cityGeneralCopies ?? []) { if (generalId === currentGeneral.id) { continue; } @@ -1399,7 +1409,7 @@ export const createReservedTurnHandler = async (options: { let deleteGeneral = false; const deletedTroopIds: number[] = []; const lifecycleSnapshot = cloneTurnGeneral(currentGeneral); - if (currentGeneral.meta.killturn <= 0) { + if (currentGeneral.meta.killturn <= 0 && typeof currentGeneral.deadYear === 'number') { if ( currentGeneral.npcState === 1 && typeof currentGeneral.deadYear === 'number' && diff --git a/app/game-engine/test/generalTurnLifecycle.test.ts b/app/game-engine/test/generalTurnLifecycle.test.ts index 33c2b4e..9097161 100644 --- a/app/game-engine/test/generalTurnLifecycle.test.ts +++ b/app/game-engine/test/generalTurnLifecycle.test.ts @@ -272,6 +272,27 @@ describe('legacy general turn lifecycle', () => { expect(harness.world.peekDirtyState().deletedGenerals).toContain(1); }); + it('keeps compatibility fixtures without legacy lifespan metadata alive', async () => { + const harness = await createTurnTestHarness({ + snapshot: makeSnapshot([ + makeGeneral({ + deadYear: undefined, + npcState: 2, + meta: { killturn: 1 }, + }), + ]), + state: makeState(), + schedule, + map, + }); + + await harness.runOneTick(); + + expect(harness.world.getGeneralById(1)).not.toBeNull(); + expect(harness.world.getGeneralById(1)!.meta.killturn).toBe(0); + expect(harness.world.peekDirtyState().lifecycleEvents[0]?.outcome).toBe('active'); + }); + it('retires a player general and resets inherited stats and rank state', async () => { const harness = await createTurnTestHarness({ snapshot: makeSnapshot([