diff --git a/scripts/verify-campaign-save-normalization.mjs b/scripts/verify-campaign-save-normalization.mjs index 7b0303e..18f40a4 100644 --- a/scripts/verify-campaign-save-normalization.mjs +++ b/scripts/verify-campaign-save-normalization.mjs @@ -36,6 +36,7 @@ try { normalizeCampaignSortieItemAssignments, resetCampaignState, setCampaignState, + setCampaignReserveTrainingFocus, setCampaignReserveTrainingAssignment, setFirstBattleReport } = await server.ssrLoadModule('/src/game/state/campaignState.ts'); @@ -155,6 +156,32 @@ try { clearedReserveDrill.reserveTrainingAssignments['liu-bei'] === undefined, `Expected reserve drill assignment API to trim, filter, and clear per-officer focus ids: ${JSON.stringify({ assignedReserveDrill, ignoredReserveDrill, clearedReserveDrill })}` ); + storage.clear(); + storage.set( + campaignStorageKey, + JSON.stringify({ + version: 1, + updatedAt: '2026-07-03T11:35:00.000Z', + step: 'third-camp', + roster: [ + { id: 'liu-bei', name: 'Liu Bei', faction: 'ally' }, + { id: 'guan-yu', name: 'Guan Yu', faction: 'ally' } + ], + reserveTrainingFocus: 'balanced', + reserveTrainingAssignments: { + 'liu-bei': 'bond-practice', + 'guan-yu': 'class-practice' + } + }) + ); + loadCampaignState(); + const dedupedReserveDrill = setCampaignReserveTrainingFocus('bond-practice'); + assert( + dedupedReserveDrill.reserveTrainingFocus === 'bond-practice' && + dedupedReserveDrill.reserveTrainingAssignments['liu-bei'] === undefined && + dedupedReserveDrill.reserveTrainingAssignments['guan-yu'] === 'class-practice', + `Expected global reserve drill focus changes to remove redundant individual assignments: ${JSON.stringify(dedupedReserveDrill.reserveTrainingAssignments)}` + ); resetCampaignState(); const firstScenario = battleScenarios['first-battle-zhuo-commandery']; diff --git a/src/game/state/campaignState.ts b/src/game/state/campaignState.ts index 8926d91..4cd6cab 100644 --- a/src/game/state/campaignState.ts +++ b/src/game/state/campaignState.ts @@ -622,7 +622,14 @@ export function markCampaignStep(step: CampaignStep) { export function setCampaignReserveTrainingFocus(focusId: CampaignReserveTrainingFocusId) { const state = ensureCampaignState(); - state.reserveTrainingFocus = normalizeReserveTrainingFocusId(focusId); + const normalizedFocusId = normalizeReserveTrainingFocusId(focusId); + const rosterUnitIds = normalizedRosterUnitIds(state.roster); + const allowedUnitIds = rosterUnitIds.size > 0 ? rosterUnitIds : undefined; + const assignments = normalizeCampaignReserveTrainingAssignments(state.reserveTrainingAssignments, allowedUnitIds); + state.reserveTrainingFocus = normalizedFocusId; + state.reserveTrainingAssignments = Object.fromEntries( + Object.entries(assignments).filter(([, assignedFocusId]) => assignedFocusId !== normalizedFocusId) + ) as CampaignReserveTrainingAssignments; state.updatedAt = new Date().toISOString(); persistCampaignState(state); return cloneCampaignState(state);