feat: connect sortie recommendations to battle results

This commit is contained in:
2026-07-13 09:50:36 +09:00
parent 7b58317f67
commit 0cadcb22b9
9 changed files with 1958 additions and 104 deletions

View File

@@ -1,5 +1,10 @@
import type { BattleBond, UnitData } from '../data/scenario';
import { isCampaignStep, type CampaignStep } from './campaignState';
import {
isCampaignStep,
normalizeCampaignSortieRecommendationSnapshot,
type CampaignSortieRecommendationSnapshot,
type CampaignStep
} from './campaignState';
import type { UnitDirection } from '../data/unitAssets';
import { equipmentExpToNext, equipmentSlots, type EquipmentSlot } from '../data/battleItems';
import type { SortieOrderId } from '../data/sortieOrders';
@@ -47,6 +52,8 @@ export type BattleSaveUnitStats = {
defeats: number;
actions: number;
support: number;
strategyUses?: number;
signatureStrategyUses?: number;
sortieBonusDamage?: number;
sortiePreventedDamage?: number;
sortieBonusHealing?: number;
@@ -83,6 +90,7 @@ export type BattleSaveState = {
campaignStep?: CampaignStep;
sortieOrderId?: SortieOrderId;
sortieResonanceBondId?: string;
sortieRecommendation?: CampaignSortieRecommendationSnapshot;
coreResonanceStats?: BattleSaveCoreResonanceStats;
savedAt: string;
turnNumber: number;
@@ -174,6 +182,15 @@ export function normalizeBattleSaveState(
}
normalizeCoreResonanceSaveFields(cloned, options);
const recommendation = normalizeCampaignSortieRecommendationSnapshot(cloned.sortieRecommendation, {
expectedBattleId: typeof cloned.battleId === 'string' ? cloned.battleId : options.expectedBattleId,
allowedUnitIds: options.validAllyUnitIds
});
if (recommendation) {
cloned.sortieRecommendation = recommendation;
} else {
delete cloned.sortieRecommendation;
}
return isValidBattleSaveState(cloned, options) ? cloned : undefined;
}
@@ -709,23 +726,32 @@ function isOptionalStatsRecord(value: unknown, options: BattleSaveValidationOpti
return false;
}
return Object.entries(value).every(
([unitId, stats]) =>
isKnownUnitId(unitId, options) &&
isRecord(stats) &&
return Object.entries(value).every(([unitId, stats]) => {
if (!isKnownUnitId(unitId, options) || !isRecord(stats)) {
return false;
}
const strategyUses = stats.strategyUses === undefined ? 0 : stats.strategyUses;
const signatureStrategyUses = stats.signatureStrategyUses === undefined ? 0 : stats.signatureStrategyUses;
return (
isBattleUnitStatValue(stats.damageDealt) &&
isBattleUnitStatValue(stats.damageTaken) &&
isBattleUnitStatValue(stats.defeats) &&
isBattleUnitStatValue(stats.actions) &&
isBattleUnitStatValue(stats.support) &&
isOptionalBattleUnitStatValue(stats.strategyUses) &&
isOptionalBattleUnitStatValue(stats.signatureStrategyUses) &&
isOptionalBattleUnitStatValue(stats.sortieBonusDamage) &&
isOptionalBattleUnitStatValue(stats.sortiePreventedDamage) &&
isOptionalBattleUnitStatValue(stats.sortieBonusHealing) &&
isOptionalBattleUnitStatValue(stats.sortieExtendedBondTriggers) &&
isOptionalBattleUnitStatValue(stats.intentDefeats) &&
isOptionalBattleUnitStatValue(stats.intentLineBreaks) &&
isOptionalBattleUnitStatValue(stats.intentGuardSuccesses)
);
isOptionalBattleUnitStatValue(stats.intentGuardSuccesses) &&
typeof strategyUses === 'number' &&
typeof signatureStrategyUses === 'number' &&
signatureStrategyUses <= strategyUses
);
});
}
function isOptionalBattleUnitStatValue(value: unknown) {