fix: guard incomplete general lifespans

This commit is contained in:
2026-07-25 08:57:53 +00:00
parent c94c5011f4
commit a83f7a44e3
2 changed files with 43 additions and 12 deletions
+22 -12
View File
@@ -1159,22 +1159,32 @@ export const createReservedTurnHandler = async (options: {
) )
); );
currentCity = currentCity ? { ...currentCity, meta: { ...currentCity.meta } } : currentCity; currentCity = currentCity ? { ...currentCity, meta: { ...currentCity.meta } } : currentCity;
const cityGeneralCopies = new Map<number, TurnGeneral>(); let cityGeneralCopies: Map<number, TurnGeneral> | undefined;
for (const general of worldView?.listGenerals() ?? []) { const getCityGeneralCopies = (): Map<number, TurnGeneral> => {
cityGeneralCopies.set( if (cityGeneralCopies) {
general.id, return cityGeneralCopies;
general.id === currentGeneral.id ? currentGeneral : cloneTurnGeneral(general) }
); cityGeneralCopies = new Map<number, TurnGeneral>();
} for (const general of worldView?.listGenerals() ?? []) {
cityGeneralCopies.set(currentGeneral.id, currentGeneral); 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 preTurnPipeline = new GeneralActionPipeline(env.generalActionModules ?? []);
const preTurnContext = createGeneralTriggerContext({ const preTurnContext = createGeneralTriggerContext({
general: currentGeneral, general: currentGeneral,
nation: currentNation, nation: currentNation,
worldView: { worldView: {
listGenerals: () => Array.from(cityGeneralCopies.values()), listGenerals: () => Array.from(getCityGeneralCopies().values()),
listGeneralsByCity: (cityId) => listGeneralsByCity: (cityId) =>
Array.from(cityGeneralCopies.values()).filter((general) => general.cityId === cityId), Array.from(getCityGeneralCopies().values()).filter((general) => general.cityId === cityId),
}, },
rng: preprocessRng, rng: preprocessRng,
log: { log: {
@@ -1209,7 +1219,7 @@ export const createReservedTurnHandler = async (options: {
} }
preTurnContext.skill.activate('pre.병력군량소모'); preTurnContext.skill.activate('pre.병력군량소모');
} }
for (const [generalId, next] of cityGeneralCopies) { for (const [generalId, next] of cityGeneralCopies ?? []) {
if (generalId === currentGeneral.id) { if (generalId === currentGeneral.id) {
continue; continue;
} }
@@ -1399,7 +1409,7 @@ export const createReservedTurnHandler = async (options: {
let deleteGeneral = false; let deleteGeneral = false;
const deletedTroopIds: number[] = []; const deletedTroopIds: number[] = [];
const lifecycleSnapshot = cloneTurnGeneral(currentGeneral); const lifecycleSnapshot = cloneTurnGeneral(currentGeneral);
if (currentGeneral.meta.killturn <= 0) { if (currentGeneral.meta.killturn <= 0 && typeof currentGeneral.deadYear === 'number') {
if ( if (
currentGeneral.npcState === 1 && currentGeneral.npcState === 1 &&
typeof currentGeneral.deadYear === 'number' && typeof currentGeneral.deadYear === 'number' &&
@@ -272,6 +272,27 @@ describe('legacy general turn lifecycle', () => {
expect(harness.world.peekDirtyState().deletedGenerals).toContain(1); 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 () => { it('retires a player general and resets inherited stats and rank state', async () => {
const harness = await createTurnTestHarness({ const harness = await createTurnTestHarness({
snapshot: makeSnapshot([ snapshot: makeSnapshot([