Harden sortie item assignment recovery

This commit is contained in:
2026-07-05 12:22:47 +09:00
parent 81bc2461ed
commit 8a73169263
2 changed files with 33 additions and 7 deletions

View File

@@ -45,9 +45,11 @@ try {
const normalizedLaunchSupplies = normalizeCampaignSortieItemAssignments(
{
'liu-bei': { bean: '9', salve: '2', stone: '4' },
'liu-bei': { ' bean ': '9', salve: '2', stone: '4', ['x'.repeat(97)]: '1' },
' liu-bei ': { wine: '1' },
'guan-yu': { wine: '3' },
'ghost-unit': { bean: '1' },
['x'.repeat(97)]: { bean: '1' },
'': { bean: '1' }
},
new Set(['liu-bei', 'guan-yu'])
@@ -56,10 +58,21 @@ try {
normalizedLaunchSupplies['liu-bei']?.bean === 2 &&
normalizedLaunchSupplies['liu-bei']?.salve === 1 &&
normalizedLaunchSupplies['liu-bei']?.stone === undefined &&
normalizedLaunchSupplies['liu-bei']?.wine === 1 &&
normalizedLaunchSupplies['liu-bei']?.['x'.repeat(97)] === undefined &&
normalizedLaunchSupplies['guan-yu']?.wine === 1 &&
normalizedLaunchSupplies['ghost-unit'] === undefined,
`Expected launch sortie supplies to share save normalization rules: ${JSON.stringify(normalizedLaunchSupplies)}`
);
const cappedLaunchSupplies = normalizeCampaignSortieItemAssignments(
Object.fromEntries(Array.from({ length: 120 }, (_, index) => [`unit-${index}`, { bean: 1 }]))
);
assert(
Object.keys(cappedLaunchSupplies).length === 96 &&
cappedLaunchSupplies['unit-95']?.bean === 1 &&
cappedLaunchSupplies['unit-96'] === undefined,
`Expected launch sortie supplies without a roster filter to stay capped: ${JSON.stringify(cappedLaunchSupplies)}`
);
resetCampaignState();
const firstScenario = battleScenarios['first-battle-zhuo-commandery'];
@@ -222,9 +235,10 @@ try {
selectedSortieUnitIds: { corrupted: true },
sortieFormationAssignments: 'front',
sortieItemAssignments: {
'liu-bei': { bean: '5', wine: -1, salve: '1', stone: '9' },
'liu-bei': { ' bean ': '5', wine: -1, salve: '1', stone: '9', ['x'.repeat(97)]: '1' },
'guan-yu': '2',
'zhang-fei': ['bean', '2'],
['x'.repeat(97)]: { bean: 1 },
'': { bean: 4 }
},
battleHistory: []
@@ -260,8 +274,10 @@ try {
malformed.sortieItemAssignments['liu-bei']?.salve === 1 &&
malformed.sortieItemAssignments['liu-bei']?.stone === undefined &&
malformed.sortieItemAssignments['liu-bei']?.wine === undefined &&
malformed.sortieItemAssignments['liu-bei']?.['x'.repeat(97)] === undefined &&
malformed.sortieItemAssignments['guan-yu'] === undefined &&
malformed.sortieItemAssignments['zhang-fei'] === undefined &&
malformed.sortieItemAssignments['x'.repeat(97)] === undefined &&
Object.keys(malformed.sortieItemAssignments).length === 1 &&
Object.keys(malformed.sortieFormationAssignments).length === 0,
`Expected malformed inventory and sortie assignments to normalize: ${JSON.stringify(malformed)}`

View File

@@ -274,6 +274,7 @@ const maxCampaignBattleObjectiveEntries = 24;
const maxCampaignBattleUnitEntries = 96;
const maxCampaignBattleBondEntries = 96;
const maxCampaignReserveTrainingEntries = 96;
const maxCampaignSortieAssignmentUnits = 96;
const maxCampaignDisplayTextLength = 180;
export const campaignReserveTrainingFocusDefinitions: CampaignReserveTrainingFocusDefinition[] = [
@@ -1176,24 +1177,33 @@ function normalizeReserveTrainingSnapshot(value: unknown): CampaignReserveTraini
export function normalizeCampaignSortieItemAssignments(assignments?: Record<string, unknown>, allowedUnitIds?: Set<string>) {
return Object.entries(assignments ?? {}).reduce<CampaignSortieItemAssignments>((next, [unitId, stocks]) => {
if (!unitId || (allowedUnitIds && !allowedUnitIds.has(unitId))) {
const normalizedUnitId = normalizeKeyString(unitId);
if (
!normalizedUnitId ||
(allowedUnitIds && !allowedUnitIds.has(normalizedUnitId)) ||
(!allowedUnitIds && Object.keys(next).length >= maxCampaignSortieAssignmentUnits)
) {
return next;
}
const unitStocks = Object.entries(recordOrEmpty(stocks)).reduce<Record<string, number>>((nextStocks, [itemId, amount]) => {
const maxPerUnit = sortieSupplyMaxByUsableId[itemId];
const normalizedItemId = normalizeKeyString(itemId);
const maxPerUnit = sortieSupplyMaxByUsableId[normalizedItemId];
if (!maxPerUnit) {
return nextStocks;
}
const normalizedAmount = normalizeNonNegativeInteger(amount);
if (itemId && normalizedAmount > 0) {
nextStocks[itemId] = Math.min(maxPerUnit, normalizedAmount);
if (normalizedItemId && normalizedAmount > 0) {
nextStocks[normalizedItemId] = Math.min(maxPerUnit, normalizedAmount);
}
return nextStocks;
}, {});
if (Object.keys(unitStocks).length > 0) {
next[unitId] = unitStocks;
next[normalizedUnitId] = {
...(next[normalizedUnitId] ?? {}),
...unitStocks
};
}
return next;
}, {});