Validate campaign save steps
This commit is contained in:
@@ -102,6 +102,21 @@ try {
|
||||
`Expected malformed inventory and sortie assignments to normalize: ${JSON.stringify(malformed)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
campaignStorageKey,
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
updatedAt: '2026-07-03T00:00:00.000Z',
|
||||
step: 'lost-between-battles',
|
||||
activeSaveSlot: 1,
|
||||
gold: 90
|
||||
})
|
||||
);
|
||||
const invalidStep = loadCampaignState();
|
||||
assert(invalidStep.step === 'new', `Expected unknown campaign step to reset safely: ${JSON.stringify(invalidStep)}`);
|
||||
assert(invalidStep.gold === 90, `Expected unknown-step save to keep harmless economy data: ${JSON.stringify(invalidStep)}`);
|
||||
|
||||
storage.clear();
|
||||
storage.set(campaignStorageKey, JSON.stringify({ version: 99, step: 'sixty-sixth-camp' }));
|
||||
const unsupported = loadCampaignState();
|
||||
@@ -122,7 +137,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 recovery, unsupported-version rejection, and slotted fallback.');
|
||||
console.log('Verified campaign save normalization, malformed-field recovery, unknown-step recovery, unsupported-version rejection, and slotted fallback.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
@@ -388,6 +388,18 @@ const campaignBattleSteps: Record<string, { victory: CampaignStep; retry: Campai
|
||||
'sixty-sixth-battle-wuzhang-final': { victory: 'sixty-sixth-camp', retry: 'sixty-sixth-battle' }
|
||||
};
|
||||
|
||||
const campaignStepIds = new Set<CampaignStep>([
|
||||
'new',
|
||||
'prologue',
|
||||
'first-victory-story',
|
||||
'ending-complete',
|
||||
'hanzhong-king-camp',
|
||||
'shu-han-foundation-camp',
|
||||
'baidi-entrustment-camp',
|
||||
'northern-campaign-prep-camp',
|
||||
...Object.values(campaignBattleSteps).flatMap(({ victory, retry }) => [victory, retry])
|
||||
]);
|
||||
|
||||
export type CampaignSaveSlotSummary = {
|
||||
slot: number;
|
||||
exists: boolean;
|
||||
@@ -787,6 +799,7 @@ function normalizeCampaignState(state: Partial<CampaignState>): CampaignState {
|
||||
};
|
||||
normalized.version = 1;
|
||||
normalized.updatedAt = typeof normalized.updatedAt === 'string' && normalized.updatedAt ? normalized.updatedAt : new Date().toISOString();
|
||||
normalized.step = normalizeCampaignStep(normalized.step);
|
||||
normalized.activeSaveSlot = normalizeSlot(normalized.activeSaveSlot);
|
||||
normalized.gold = normalizeNonNegativeInteger(normalized.gold);
|
||||
normalized.roster = arrayOrEmpty<UnitData>(normalized.roster).map(cloneUnit);
|
||||
@@ -861,6 +874,10 @@ function normalizeReserveTrainingFocusId(focusId?: string): CampaignReserveTrain
|
||||
: defaultCampaignReserveTrainingFocusId;
|
||||
}
|
||||
|
||||
function normalizeCampaignStep(step: unknown): CampaignStep {
|
||||
return typeof step === 'string' && campaignStepIds.has(step as CampaignStep) ? step as CampaignStep : 'new';
|
||||
}
|
||||
|
||||
function reserveTrainingFocusDefinition(focusId?: string) {
|
||||
const normalizedId = normalizeReserveTrainingFocusId(focusId);
|
||||
return campaignReserveTrainingFocusDefinitions.find((focus) => focus.id === normalizedId) ?? campaignReserveTrainingFocusDefinitions[0];
|
||||
|
||||
Reference in New Issue
Block a user