From 49b2b2300fbb4695e4776a5167d6fdc580b08dc8 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 09:45:56 +0900 Subject: [PATCH] Recover stale latest battle ids --- scripts/verify-campaign-save-normalization.mjs | 7 ++++++- src/game/state/campaignState.ts | 10 ++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/scripts/verify-campaign-save-normalization.mjs b/scripts/verify-campaign-save-normalization.mjs index 417fbdb..1265ecb 100644 --- a/scripts/verify-campaign-save-normalization.mjs +++ b/scripts/verify-campaign-save-normalization.mjs @@ -163,6 +163,7 @@ try { version: 1, updatedAt: '2026-07-04T01:00:00.000Z', step: 'fifth-camp', + latestBattleId: 'missing-history-entry', battleHistory: { 'wrong-history-key': { battleId: 'first-battle-zhuo-commandery', @@ -230,6 +231,10 @@ try { malformedHistory.battleHistory['first-battle-zhuo-commandery']?.campaignRewards?.recruits.length === 1, `Expected nested battle history rewards to normalize: ${JSON.stringify(malformedHistory)}` ); + assert( + malformedHistory.latestBattleId === 'first-battle-zhuo-commandery', + `Expected stale latestBattleId to recover to the latest valid settlement: ${JSON.stringify(malformedHistory)}` + ); assert( malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives.length === 1 && malformedHistory.battleHistory['first-battle-zhuo-commandery']?.objectives[0].rewardGold === 80 && @@ -264,7 +269,7 @@ try { const fallback = loadCampaignState(); assert(fallback.step === 'second-camp' && fallback.activeSaveSlot === 2, `Expected valid slotted save fallback: ${JSON.stringify(fallback)}`); - console.log('Verified campaign save normalization, malformed-field/bond/sortie-item/history-key/detail recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.'); + console.log('Verified campaign save normalization, malformed-field/bond/sortie-item/latest-history/detail recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.'); } finally { await server.close(); } diff --git a/src/game/state/campaignState.ts b/src/game/state/campaignState.ts index 10a84f2..cb76aee 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -816,6 +816,7 @@ function normalizeCampaignState(state: Partial): CampaignState { normalized.sortieItemAssignments = normalizeSortieItemAssignments(recordOrEmpty(normalized.sortieItemAssignments)); normalized.reserveTrainingFocus = normalizeReserveTrainingFocusId(normalized.reserveTrainingFocus); normalized.battleHistory = normalizeBattleHistory(normalized.battleHistory); + normalized.latestBattleId = normalizeLatestBattleId(normalized.latestBattleId, normalized.battleHistory); normalized.firstBattleReport = normalizeFirstBattleReport(normalized.firstBattleReport); return normalized; } @@ -863,6 +864,15 @@ function normalizeBattleHistory(value: unknown) { }, {}); } +function normalizeLatestBattleId(value: unknown, battleHistory: Record) { + if (typeof value === 'string' && value in battleHistory) { + return value; + } + + return Object.values(battleHistory) + .sort((a, b) => String(b.completedAt).localeCompare(String(a.completedAt)))[0]?.battleId; +} + function normalizeCampaignBattleSettlement(value: unknown): CampaignBattleSettlement | undefined { if (!isPlainObject(value)) { return undefined;