diff --git a/scripts/verify-campaign-save-normalization.mjs b/scripts/verify-campaign-save-normalization.mjs index 11ef7bd..f1c7bce 100644 --- a/scripts/verify-campaign-save-normalization.mjs +++ b/scripts/verify-campaign-save-normalization.mjs @@ -153,6 +153,33 @@ try { `Expected stale sortie ids to be filtered against the recovered roster: ${JSON.stringify(staleSortie)}` ); + storage.clear(); + storage.set( + campaignStorageKey, + JSON.stringify({ + version: 1, + updatedAt: '2026-07-03T13:00:00.000Z', + step: 'third-camp', + activeSaveSlot: 1, + roster: [ + { id: 'liu-bei', name: 'Liu Bei', faction: 'ally' }, + { id: 'guan-yu', name: 'Guan Yu', faction: 'ally' } + ], + bonds: [ + { id: 'liu-bei__guan-yu', title: 'Oath Bond', unitIds: ['liu-bei', 'guan-yu'], level: '2', exp: '30', battleExp: '4' }, + { id: 'liu-bei__ghost', title: 'Ghost Bond', unitIds: ['liu-bei', 'ghost-unit'], level: '3', exp: '40', battleExp: '5' } + ] + }) + ); + const staleBonds = loadCampaignState(); + assert( + staleBonds.bonds.length === 1 && + staleBonds.bonds[0].id === 'liu-bei__guan-yu' && + staleBonds.bonds[0].level === 2 && + staleBonds.bonds[0].battleExp === 4, + `Expected stale bonds to be filtered against the recovered roster while valid bonds survive: ${JSON.stringify(staleBonds)}` + ); + storage.clear(); storage.set( campaignStorageKey, @@ -304,7 +331,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-roster/latest-history/detail recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.'); + console.log('Verified campaign save normalization, malformed-field/bond-roster/sortie-roster/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 388f9ee..3279de0 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -813,6 +813,9 @@ function normalizeCampaignState(state: Partial): CampaignState { normalized.inventory = normalizeInventory(normalized.inventory); const rosterUnitIds = normalizedRosterUnitIds(normalized.roster); const sortieUnitFilter = rosterUnitIds.size > 0 ? rosterUnitIds : undefined; + if (sortieUnitFilter) { + normalized.bonds = normalized.bonds.filter((bond) => bond.unitIds.every((unitId) => sortieUnitFilter.has(unitId))); + } normalized.selectedSortieUnitIds = uniqueStrings(normalized.selectedSortieUnitIds) .filter((unitId) => !sortieUnitFilter || sortieUnitFilter.has(unitId)); normalized.sortieFormationAssignments = normalizeSortieFormationAssignments(recordOrEmpty(normalized.sortieFormationAssignments), sortieUnitFilter);