feat: add sortie operation orders
This commit is contained in:
@@ -35,6 +35,7 @@ try {
|
||||
normalizeCampaignReserveTrainingAssignments,
|
||||
normalizeCampaignSortieFormationPresets,
|
||||
normalizeCampaignSortieItemAssignments,
|
||||
normalizeCampaignSortieOrderResultSnapshot,
|
||||
normalizeCampaignSortiePerformanceSnapshot,
|
||||
normalizeCampaignSortieReviewSnapshot,
|
||||
resetCampaignState,
|
||||
@@ -42,6 +43,7 @@ try {
|
||||
setCampaignState,
|
||||
setCampaignReserveTrainingFocus,
|
||||
setCampaignReserveTrainingAssignment,
|
||||
setCampaignSortieOrderSelection,
|
||||
setFirstBattleReport
|
||||
} = await server.ssrLoadModule('/src/game/state/campaignState.ts');
|
||||
const { battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts');
|
||||
@@ -300,6 +302,138 @@ try {
|
||||
)),
|
||||
`Expected persisted sortie review grades to match evaluator boundaries: ${JSON.stringify(sortieReviewGradeBoundaries)}`
|
||||
);
|
||||
const normalizedEliteOrder = normalizeCampaignSortieOrderResultSnapshot({
|
||||
version: 1,
|
||||
orderId: 'elite',
|
||||
score: '999',
|
||||
achieved: false,
|
||||
rewardGranted: true,
|
||||
progress: [
|
||||
{ id: 'elite-survival', achieved: true, value: '3.9', target: '3.2' },
|
||||
{ id: 'victory', achieved: true, value: '1', target: '0' },
|
||||
{ id: 'elite-grade', achieved: false, value: '-8', target: '75' },
|
||||
{ id: 'ghost-check', achieved: true, value: 999, target: 1 }
|
||||
]
|
||||
});
|
||||
assert(
|
||||
normalizedEliteOrder?.version === 1 &&
|
||||
normalizedEliteOrder.orderId === 'elite' &&
|
||||
normalizedEliteOrder.score === 100 &&
|
||||
normalizedEliteOrder.achieved === false &&
|
||||
normalizedEliteOrder.rewardGranted === false &&
|
||||
JSON.stringify(normalizedEliteOrder.progress.map((entry) => entry.id)) ===
|
||||
JSON.stringify(['victory', 'elite-grade', 'elite-survival']) &&
|
||||
normalizedEliteOrder.progress[0].target === 1 &&
|
||||
normalizedEliteOrder.progress[1].value === 0 &&
|
||||
normalizedEliteOrder.progress[2].value === 3 &&
|
||||
normalizedEliteOrder.progress[2].target === 3,
|
||||
`Expected sortie orders to canonicalize checks, clamp score/numbers, derive completion, and reject unearned reward flags: ${JSON.stringify(normalizedEliteOrder)}`
|
||||
);
|
||||
const normalizedMissingMobileCheck = normalizeCampaignSortieOrderResultSnapshot({
|
||||
version: 1,
|
||||
orderId: 'mobile',
|
||||
score: 90,
|
||||
achieved: true,
|
||||
rewardGranted: true,
|
||||
progress: [
|
||||
{ id: 'victory', achieved: true, value: 1, target: 1 },
|
||||
{ id: 'mobile-quick', achieved: true, value: 8, target: 8 }
|
||||
]
|
||||
});
|
||||
assert(
|
||||
normalizedMissingMobileCheck?.achieved === false &&
|
||||
normalizedMissingMobileCheck.rewardGranted === false &&
|
||||
normalizedMissingMobileCheck.progress.find((entry) => entry.id === 'mobile-breakthrough')?.achieved === false &&
|
||||
normalizeCampaignSortieOrderResultSnapshot({ ...normalizedEliteOrder, version: 2 }) === undefined &&
|
||||
normalizeCampaignSortieOrderResultSnapshot({ ...normalizedEliteOrder, orderId: 'ghost' }) === undefined,
|
||||
`Expected missing checks and unsupported sortie-order versions/ids to fail safely: ${JSON.stringify(normalizedMissingMobileCheck)}`
|
||||
);
|
||||
const normalizedStringBooleans = normalizeCampaignSortieOrderResultSnapshot({
|
||||
...normalizedEliteOrder,
|
||||
achieved: 'false',
|
||||
rewardGranted: 'false',
|
||||
progress: normalizedEliteOrder.progress.map((entry) => ({ ...entry, achieved: 'false' }))
|
||||
});
|
||||
assert(
|
||||
normalizedStringBooleans?.achieved === false &&
|
||||
normalizedStringBooleans.rewardGranted === false &&
|
||||
normalizedStringBooleans.progress.every((entry) => entry.achieved === false),
|
||||
`Expected malformed string booleans to stay false instead of restoring an unearned reward claim: ${JSON.stringify(normalizedStringBooleans)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
campaignStorageKey,
|
||||
JSON.stringify({
|
||||
version: 1,
|
||||
updatedAt: '2026-07-03T11:10:00.000Z',
|
||||
step: 'first-camp',
|
||||
sortieOrderSelection: { battleId: 'second-battle-yellow-turban-pursuit', orderId: 'mobile', extra: true },
|
||||
sortieOrderHistory: {
|
||||
'first-battle-zhuo-commandery': {
|
||||
elite: {
|
||||
version: 1,
|
||||
orderId: 'elite',
|
||||
score: 101,
|
||||
achieved: false,
|
||||
rewardGranted: true,
|
||||
progress: [
|
||||
{ id: 'victory', achieved: true, value: 1, target: 1 },
|
||||
{ id: 'elite-grade', achieved: true, value: 90, target: 75 },
|
||||
{ id: 'elite-survival', achieved: true, value: 3, target: 3 },
|
||||
{ id: 'ghost-check', achieved: true, value: 1, target: 1 }
|
||||
]
|
||||
},
|
||||
mobile: { ...normalizedEliteOrder, orderId: 'elite' },
|
||||
ghost: normalizedEliteOrder
|
||||
},
|
||||
'unknown-battle': { elite: normalizedEliteOrder },
|
||||
corrupted: 'broken'
|
||||
},
|
||||
claimedSortieOrderRewardIds: [
|
||||
'first-battle-zhuo-commandery:elite',
|
||||
'first-battle-zhuo-commandery:elite',
|
||||
'second-battle-yellow-turban-pursuit:mobile',
|
||||
'unknown-battle:elite',
|
||||
'first-battle-zhuo-commandery:ghost',
|
||||
7
|
||||
]
|
||||
})
|
||||
);
|
||||
const normalizedOrderState = loadCampaignState();
|
||||
const normalizedHistoricalElite = normalizedOrderState.sortieOrderHistory['first-battle-zhuo-commandery']?.elite;
|
||||
assert(
|
||||
JSON.stringify(normalizedOrderState.sortieOrderSelection) ===
|
||||
JSON.stringify({ battleId: 'second-battle-yellow-turban-pursuit', orderId: 'mobile' }) &&
|
||||
Object.keys(normalizedOrderState.sortieOrderHistory).length === 1 &&
|
||||
Object.keys(normalizedOrderState.sortieOrderHistory['first-battle-zhuo-commandery']).length === 1 &&
|
||||
normalizedHistoricalElite?.score === 100 &&
|
||||
normalizedHistoricalElite.achieved === true &&
|
||||
normalizedHistoricalElite.rewardGranted === true &&
|
||||
JSON.stringify(normalizedOrderState.claimedSortieOrderRewardIds.sort()) === JSON.stringify([
|
||||
'first-battle-zhuo-commandery:elite',
|
||||
'second-battle-yellow-turban-pursuit:mobile'
|
||||
]),
|
||||
`Expected sortie-order selection/history/claims to whitelist battles and ids, dedupe claims, and recover reward claims from history: ${JSON.stringify(normalizedOrderState)}`
|
||||
);
|
||||
|
||||
const selectedOrderState = setCampaignSortieOrderSelection('first-battle-zhuo-commandery', 'siege');
|
||||
assert(
|
||||
JSON.stringify(selectedOrderState.sortieOrderSelection) ===
|
||||
JSON.stringify({ battleId: 'first-battle-zhuo-commandery', orderId: 'siege' }),
|
||||
`Expected explicit sortie-order selection to bind to one battle: ${JSON.stringify(selectedOrderState.sortieOrderSelection)}`
|
||||
);
|
||||
const invalidOrderSelection = setCampaignSortieOrderSelection('unknown-battle', 'elite');
|
||||
assert(
|
||||
JSON.stringify(invalidOrderSelection.sortieOrderSelection) ===
|
||||
JSON.stringify({ battleId: 'first-battle-zhuo-commandery', orderId: 'siege' }),
|
||||
`Expected an unknown battle not to disturb the current sortie-order selection: ${JSON.stringify(invalidOrderSelection.sortieOrderSelection)}`
|
||||
);
|
||||
const clearedOrderSelection = setCampaignSortieOrderSelection('first-battle-zhuo-commandery');
|
||||
assert(
|
||||
clearedOrderSelection.sortieOrderSelection === undefined,
|
||||
`Expected clearing the matching battle order to remove the scoped selection: ${JSON.stringify(clearedOrderSelection.sortieOrderSelection)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
@@ -738,6 +872,257 @@ try {
|
||||
`Expected campaign recruit rewards to merge from templates once during repeated settlement: ${JSON.stringify(repeatedRecruitSettlement)}`
|
||||
);
|
||||
|
||||
const orderPerformance = {
|
||||
selectedUnitIds: [performanceUnit.id],
|
||||
formationAssignments: { [performanceUnit.id]: 'front' },
|
||||
unitStats: [sortieStatsFixture(performanceUnit.id, {
|
||||
hpBefore: performanceUnit.maxHp,
|
||||
maxHpBefore: performanceUnit.maxHp,
|
||||
damageDealt: 24,
|
||||
sortiePreventedDamage: 4
|
||||
})]
|
||||
};
|
||||
const orderReview = {
|
||||
version: 1,
|
||||
score: 88,
|
||||
grade: 'A',
|
||||
activeBondCount: 1,
|
||||
enemyCount: 4,
|
||||
sourcePresetIds: ['elite']
|
||||
};
|
||||
const successfulEliteOrder = {
|
||||
version: 1,
|
||||
orderId: 'elite',
|
||||
score: 88,
|
||||
achieved: true,
|
||||
rewardGranted: false,
|
||||
progress: [
|
||||
{ id: 'victory', achieved: true, value: 1, target: 1 },
|
||||
{ id: 'elite-grade', achieved: true, value: 88, target: 75 },
|
||||
{ id: 'elite-survival', achieved: true, value: 1, target: 1 }
|
||||
]
|
||||
};
|
||||
const successfulMobileOrder = {
|
||||
version: 1,
|
||||
orderId: 'mobile',
|
||||
score: 88,
|
||||
achieved: true,
|
||||
rewardGranted: false,
|
||||
progress: [
|
||||
{ id: 'victory', achieved: true, value: 1, target: 1 },
|
||||
{ id: 'mobile-quick', achieved: true, value: 5, target: 15 },
|
||||
{ id: 'mobile-breakthrough', achieved: true, value: 24, target: 1 }
|
||||
]
|
||||
};
|
||||
const orderBattleReport = {
|
||||
...firstBattleReport,
|
||||
sortiePerformance: orderPerformance,
|
||||
sortieReview: orderReview,
|
||||
sortieOrder: successfulEliteOrder,
|
||||
createdAt: '2026-07-03T11:20:00.000Z'
|
||||
};
|
||||
const orderBattleReplay = JSON.parse(JSON.stringify(orderBattleReport));
|
||||
|
||||
resetCampaignState();
|
||||
setCampaignSortieOrderSelection(firstScenario.id, 'mobile');
|
||||
setFirstBattleReport({
|
||||
...orderBattleReplay,
|
||||
outcome: 'defeat',
|
||||
rewardGold: 0,
|
||||
itemRewards: [],
|
||||
sortieOrder: {
|
||||
...successfulMobileOrder,
|
||||
achieved: false,
|
||||
progress: successfulMobileOrder.progress.map((entry) => entry.id === 'victory'
|
||||
? { ...entry, achieved: false, value: 0 }
|
||||
: { ...entry })
|
||||
},
|
||||
createdAt: '2026-07-03T11:19:00.000Z'
|
||||
});
|
||||
const defeatedOrderSettlement = getCampaignState();
|
||||
assert(
|
||||
JSON.stringify(defeatedOrderSettlement.sortieOrderSelection) ===
|
||||
JSON.stringify({ battleId: firstScenario.id, orderId: 'mobile' }) &&
|
||||
defeatedOrderSettlement.sortieOrderHistory[firstScenario.id]?.mobile?.achieved === false &&
|
||||
defeatedOrderSettlement.claimedSortieOrderRewardIds.length === 0 &&
|
||||
defeatedOrderSettlement.gold === 0 &&
|
||||
defeatedOrderSettlement.battleHistory[firstScenario.id] === undefined,
|
||||
`Expected defeat to record the failed order without granting rewards or clearing the retry-scoped selection: ${JSON.stringify(defeatedOrderSettlement)}`
|
||||
);
|
||||
|
||||
resetCampaignState();
|
||||
setCampaignSortieOrderSelection(firstScenario.id, 'elite');
|
||||
const firstOrderCanonicalReport = setFirstBattleReport(orderBattleReport);
|
||||
const firstOrderSettlement = getCampaignState();
|
||||
assert(
|
||||
firstOrderCanonicalReport?.sortieOrder?.rewardGranted === true &&
|
||||
firstOrderSettlement.gold === 280 &&
|
||||
firstOrderSettlement.inventory.Bean === 2 &&
|
||||
firstOrderSettlement.inventory['Iron Sword'] === 1 &&
|
||||
firstOrderSettlement.inventory['상처약'] === 1 &&
|
||||
firstOrderSettlement.claimedSortieOrderRewardIds.length === 1 &&
|
||||
firstOrderSettlement.claimedSortieOrderRewardIds[0] === `${firstScenario.id}:elite` &&
|
||||
firstOrderSettlement.sortieOrderSelection === undefined &&
|
||||
firstOrderSettlement.firstBattleReport?.sortieOrder?.rewardGranted === true &&
|
||||
firstOrderSettlement.battleHistory[firstScenario.id]?.sortieOrder?.rewardGranted === true &&
|
||||
firstOrderSettlement.sortieOrderHistory[firstScenario.id]?.elite?.rewardGranted === true,
|
||||
`Expected the first battle/order success to grant its independent reward once, persist every snapshot, and clear the matching selection: ${JSON.stringify(firstOrderSettlement)}`
|
||||
);
|
||||
const reloadedFirstOrderSettlement = loadCampaignState();
|
||||
assert(
|
||||
reloadedFirstOrderSettlement.firstBattleReport?.sortieOrder?.achieved === true &&
|
||||
reloadedFirstOrderSettlement.firstBattleReport.sortieOrder.rewardGranted === true &&
|
||||
reloadedFirstOrderSettlement.battleHistory[firstScenario.id]?.sortieOrder?.achieved === true &&
|
||||
reloadedFirstOrderSettlement.battleHistory[firstScenario.id].sortieOrder.rewardGranted === true &&
|
||||
reloadedFirstOrderSettlement.sortieOrderHistory[firstScenario.id]?.elite?.achieved === true &&
|
||||
reloadedFirstOrderSettlement.claimedSortieOrderRewardIds.includes(`${firstScenario.id}:elite`),
|
||||
`Expected report, settlement, independent history, and reward claim to survive save normalization together: ${JSON.stringify(reloadedFirstOrderSettlement)}`
|
||||
);
|
||||
|
||||
orderBattleReport.sortieOrder.progress[0].achieved = false;
|
||||
firstOrderCanonicalReport.sortieOrder.progress[1].achieved = false;
|
||||
const deepCopyOrderState = getCampaignState();
|
||||
const detachedOrderState = getCampaignState();
|
||||
detachedOrderState.firstBattleReport.sortieOrder.progress[2].achieved = false;
|
||||
assert(
|
||||
deepCopyOrderState.firstBattleReport?.sortieOrder?.progress.every((entry) => entry.achieved) &&
|
||||
deepCopyOrderState.battleHistory[firstScenario.id]?.sortieOrder?.progress.every((entry) => entry.achieved) &&
|
||||
deepCopyOrderState.sortieOrderHistory[firstScenario.id]?.elite?.progress.every((entry) => entry.achieved) &&
|
||||
detachedOrderState.battleHistory[firstScenario.id]?.sortieOrder?.progress[2].achieved === true &&
|
||||
getCampaignState().firstBattleReport?.sortieOrder?.progress[2].achieved === true,
|
||||
`Expected input, returned report, report/history, and returned campaign sortie-order progress to be deeply detached: ${JSON.stringify(deepCopyOrderState)}`
|
||||
);
|
||||
|
||||
const repeatedOrderCanonicalReport = setFirstBattleReport(orderBattleReplay);
|
||||
const repeatedOrderSettlement = getCampaignState();
|
||||
assert(
|
||||
repeatedOrderCanonicalReport?.sortieOrder?.rewardGranted === false &&
|
||||
repeatedOrderSettlement.gold === 280 &&
|
||||
repeatedOrderSettlement.inventory['상처약'] === 1 &&
|
||||
repeatedOrderSettlement.claimedSortieOrderRewardIds.length === 1,
|
||||
`Expected repeated settlement of the same battle/order to avoid duplicate gold and supplies: ${JSON.stringify(repeatedOrderSettlement)}`
|
||||
);
|
||||
|
||||
const revisedOrderReport = {
|
||||
...orderBattleReplay,
|
||||
rewardGold: 140,
|
||||
itemRewards: ['Bean x3', 'Wine', 'Iron Armor 1'],
|
||||
campaignRewards: {
|
||||
supplies: ['Bean x3', 'Wine'],
|
||||
equipment: ['Iron Armor 1'],
|
||||
reputation: [],
|
||||
recruits: [],
|
||||
unlocks: firstScenarioUnlocks
|
||||
},
|
||||
createdAt: '2026-07-03T11:21:00.000Z'
|
||||
};
|
||||
setFirstBattleReport(revisedOrderReport);
|
||||
const revisedOrderSettlement = getCampaignState();
|
||||
assert(
|
||||
revisedOrderSettlement.gold === 320 &&
|
||||
revisedOrderSettlement.inventory.Bean === 3 &&
|
||||
revisedOrderSettlement.inventory.Wine === 1 &&
|
||||
revisedOrderSettlement.inventory['Iron Sword'] === undefined &&
|
||||
revisedOrderSettlement.inventory['Iron Armor'] === 1 &&
|
||||
revisedOrderSettlement.inventory['상처약'] === 1 &&
|
||||
revisedOrderSettlement.claimedSortieOrderRewardIds.length === 1,
|
||||
`Expected ordinary reward replacement to preserve the separately claimed sortie-order reward: ${JSON.stringify(revisedOrderSettlement)}`
|
||||
);
|
||||
|
||||
setFirstBattleReport({
|
||||
...revisedOrderReport,
|
||||
sortieOrder: successfulMobileOrder,
|
||||
createdAt: '2026-07-03T11:22:00.000Z'
|
||||
});
|
||||
const secondOrderSameBattle = getCampaignState();
|
||||
assert(
|
||||
secondOrderSameBattle.gold === 480 &&
|
||||
secondOrderSameBattle.inventory['상처약'] === 1 &&
|
||||
secondOrderSameBattle.inventory['탁주'] === 1 &&
|
||||
secondOrderSameBattle.claimedSortieOrderRewardIds.includes(`${firstScenario.id}:elite`) &&
|
||||
secondOrderSameBattle.claimedSortieOrderRewardIds.includes(`${firstScenario.id}:mobile`) &&
|
||||
secondOrderSameBattle.sortieOrderHistory[firstScenario.id]?.elite?.achieved === true &&
|
||||
secondOrderSameBattle.sortieOrderHistory[firstScenario.id]?.mobile?.achieved === true,
|
||||
`Expected a different order on the same battle to own an independent first reward and history cell: ${JSON.stringify(secondOrderSameBattle)}`
|
||||
);
|
||||
|
||||
const failedEliteOrder = {
|
||||
...successfulEliteOrder,
|
||||
achieved: false,
|
||||
progress: successfulEliteOrder.progress.map((entry) => entry.id === 'elite-survival'
|
||||
? { ...entry, achieved: false, value: 0 }
|
||||
: { ...entry })
|
||||
};
|
||||
setFirstBattleReport({
|
||||
...revisedOrderReport,
|
||||
sortieOrder: failedEliteOrder,
|
||||
createdAt: '2026-07-03T11:23:00.000Z'
|
||||
});
|
||||
const failedEliteReplay = getCampaignState();
|
||||
assert(
|
||||
failedEliteReplay.gold === 480 &&
|
||||
failedEliteReplay.inventory['상처약'] === 1 &&
|
||||
failedEliteReplay.claimedSortieOrderRewardIds.includes(`${firstScenario.id}:elite`) &&
|
||||
failedEliteReplay.firstBattleReport?.sortieOrder?.achieved === false &&
|
||||
failedEliteReplay.battleHistory[firstScenario.id]?.sortieOrder?.achieved === false &&
|
||||
failedEliteReplay.sortieOrderHistory[firstScenario.id]?.elite?.achieved === true &&
|
||||
failedEliteReplay.sortieOrderHistory[firstScenario.id]?.elite?.score === 88 &&
|
||||
failedEliteReplay.sortieOrderHistory[firstScenario.id]?.mobile?.achieved === true,
|
||||
`Expected a failed retry to remain current in report/settlement while preserving the best earned history and first reward: ${JSON.stringify(failedEliteReplay)}`
|
||||
);
|
||||
|
||||
const secondScenario = battleScenarios['second-battle-yellow-turban-pursuit'];
|
||||
setCampaignSortieOrderSelection(secondScenario.id, 'elite');
|
||||
setFirstBattleReport({
|
||||
...orderBattleReplay,
|
||||
battleId: secondScenario.id,
|
||||
battleTitle: secondScenario.title,
|
||||
rewardGold: 120,
|
||||
itemRewards: [],
|
||||
campaignRewards: { supplies: [], equipment: [], reputation: [], recruits: [], unlocks: [] },
|
||||
units: secondScenario.units.filter((unit) => unit.faction === 'ally'),
|
||||
bonds: secondScenario.bonds.map((bond) => ({ ...bond, battleExp: 0 })),
|
||||
sortieOrder: orderBattleReplay.sortieOrder,
|
||||
createdAt: '2026-07-03T11:24:00.000Z'
|
||||
});
|
||||
const sameOrderDifferentBattle = getCampaignState();
|
||||
assert(
|
||||
sameOrderDifferentBattle.gold === 780 &&
|
||||
sameOrderDifferentBattle.inventory['상처약'] === 2 &&
|
||||
sameOrderDifferentBattle.claimedSortieOrderRewardIds.includes(`${secondScenario.id}:elite`) &&
|
||||
sameOrderDifferentBattle.claimedSortieOrderRewardIds.length === 3 &&
|
||||
sameOrderDifferentBattle.sortieOrderSelection === undefined,
|
||||
`Expected the same order on a different battle to receive its own first reward exactly once: ${JSON.stringify(sameOrderDifferentBattle)}`
|
||||
);
|
||||
|
||||
resetCampaignState();
|
||||
setFirstBattleReport({
|
||||
...orderBattleReplay,
|
||||
rewardGold: 930,
|
||||
itemRewards: ['탁주 1'],
|
||||
sortieOrder: undefined,
|
||||
createdAt: '2026-07-03T11:25:00.000Z'
|
||||
});
|
||||
const spentPreviousRewards = getCampaignState();
|
||||
spentPreviousRewards.gold = 0;
|
||||
delete spentPreviousRewards.inventory['탁주'];
|
||||
setCampaignState(spentPreviousRewards);
|
||||
setFirstBattleReport({
|
||||
...orderBattleReplay,
|
||||
rewardGold: 600,
|
||||
itemRewards: ['탁주 1'],
|
||||
sortieOrder: successfulMobileOrder,
|
||||
createdAt: '2026-07-03T11:26:00.000Z'
|
||||
});
|
||||
const spentRewardReplay = getCampaignState();
|
||||
assert(
|
||||
spentRewardReplay.gold === 160 &&
|
||||
spentRewardReplay.inventory['탁주'] === 2 &&
|
||||
spentRewardReplay.claimedSortieOrderRewardIds.includes(`${firstScenario.id}:mobile`) &&
|
||||
spentRewardReplay.firstBattleReport?.sortieOrder?.rewardGranted === true,
|
||||
`Expected a first order reward to remain intact even when previously settled ordinary gold/items were already spent: ${JSON.stringify(spentRewardReplay)}`
|
||||
);
|
||||
|
||||
storage.clear();
|
||||
storage.set(
|
||||
campaignStorageKey,
|
||||
@@ -765,6 +1150,11 @@ try {
|
||||
typeof legacy.sortieItemAssignments === 'object' &&
|
||||
typeof legacy.sortieFormationPresets === 'object' &&
|
||||
Object.keys(legacy.sortieFormationPresets).length === 0 &&
|
||||
legacy.sortieOrderSelection === undefined &&
|
||||
typeof legacy.sortieOrderHistory === 'object' &&
|
||||
Object.keys(legacy.sortieOrderHistory).length === 0 &&
|
||||
Array.isArray(legacy.claimedSortieOrderRewardIds) &&
|
||||
legacy.claimedSortieOrderRewardIds.length === 0 &&
|
||||
typeof legacy.reserveTrainingAssignments === 'object',
|
||||
`Expected modern sortie fields to be restored: ${JSON.stringify(legacy)}`
|
||||
);
|
||||
@@ -1648,7 +2038,7 @@ try {
|
||||
`Expected explicitly loaded corrupted slot timestamp to be normalized: ${JSON.stringify(corruptedSlot)}`
|
||||
);
|
||||
|
||||
console.log('Verified campaign save normalization, reward resettlement, camp reward idempotence, malformed-field/roster-equipment/bond-growth/bond-roster/report-reference/sortie-roster/sortie-preset/sortie-performance/sortie-review/latest-history/detail recovery, sortie performance/review legacy/deep-clone safety, timestamp-safe history/slot recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.');
|
||||
console.log('Verified campaign save normalization, reward resettlement, camp reward idempotence, malformed-field/roster-equipment/bond-growth/bond-roster/report-reference/sortie-roster/sortie-preset/sortie-performance/sortie-review/sortie-order/latest-history/detail recovery, sortie performance/review/order legacy/deep-clone/retry/reward-idempotence safety, timestamp-safe history/slot recovery, unknown-step/report/history recovery, unsupported-version rejection, and slotted fallback.');
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user