feat: connect sortie recommendations to battle results
This commit is contained in:
@@ -37,6 +37,7 @@ try {
|
||||
normalizeCampaignSortieItemAssignments,
|
||||
normalizeCampaignSortieOrderResultSnapshot,
|
||||
normalizeCampaignSortiePerformanceSnapshot,
|
||||
normalizeCampaignSortieRecommendationSnapshot,
|
||||
normalizeCampaignSortieReviewSnapshot,
|
||||
resetCampaignState,
|
||||
saveCampaignState,
|
||||
@@ -73,6 +74,39 @@ try {
|
||||
const empty = loadCampaignState();
|
||||
assert(empty.version === 1 && empty.step === 'new', `Expected empty storage to create initial campaign state: ${JSON.stringify(empty)}`);
|
||||
|
||||
const normalizedRecommendation = normalizeCampaignSortieRecommendationSnapshot({
|
||||
version: 1,
|
||||
battleId: 'first-battle-zhuo-commandery',
|
||||
planId: 'terrain-strategy',
|
||||
label: '지형·전법형',
|
||||
summary: '지형과 고유 전법을 함께 활용합니다.',
|
||||
sortieOrderId: 'elite',
|
||||
selectedUnitIds: ['liu-bei', 'guan-yu'],
|
||||
formationAssignments: { 'liu-bei': 'support', 'guan-yu': 'front', 'ghost-unit': 'flank' },
|
||||
unitReasons: { 'liu-bei': '고유 인덕의 호령', 'guan-yu': '고유 청룡일섬', 'ghost-unit': '제외' }
|
||||
}, {
|
||||
expectedBattleId: 'first-battle-zhuo-commandery',
|
||||
allowedUnitIds: new Set(['liu-bei', 'guan-yu'])
|
||||
});
|
||||
assert(
|
||||
normalizedRecommendation?.planId === 'terrain-strategy' &&
|
||||
JSON.stringify(normalizedRecommendation.selectedUnitIds) === JSON.stringify(['liu-bei', 'guan-yu']) &&
|
||||
Object.keys(normalizedRecommendation.unitReasons).length === 2 &&
|
||||
normalizedRecommendation.formationAssignments['ghost-unit'] === undefined,
|
||||
`Expected recommendation provenance to normalize with its selected roster: ${JSON.stringify(normalizedRecommendation)}`
|
||||
);
|
||||
assert(
|
||||
normalizeCampaignSortieRecommendationSnapshot(
|
||||
{ ...normalizedRecommendation, planId: 'unknown-plan' },
|
||||
{ expectedBattleId: 'first-battle-zhuo-commandery' }
|
||||
) === undefined &&
|
||||
normalizeCampaignSortieRecommendationSnapshot(
|
||||
{ ...normalizedRecommendation, unitReasons: { 'liu-bei': '', 'guan-yu': '유효' } },
|
||||
{ expectedBattleId: 'first-battle-zhuo-commandery' }
|
||||
) === undefined,
|
||||
'Expected unknown recommendation plans and missing officer reasons to normalize away.'
|
||||
);
|
||||
|
||||
const normalizedLaunchSupplies = normalizeCampaignSortieItemAssignments(
|
||||
{
|
||||
'liu-bei': { ' bean ': '9', salve: '2', stone: '4', ['x'.repeat(97)]: '1' },
|
||||
@@ -195,8 +229,10 @@ try {
|
||||
damageDealt: '1000000',
|
||||
damageTaken: -4,
|
||||
defeats: '2.9',
|
||||
actions: 'not-a-number',
|
||||
support: '7',
|
||||
actions: 'not-a-number',
|
||||
support: '7',
|
||||
strategyUses: '2',
|
||||
signatureStrategyUses: '9',
|
||||
sortieBonusDamage: '3',
|
||||
sortiePreventedDamage: '4',
|
||||
sortieBonusHealing: '5',
|
||||
@@ -228,8 +264,10 @@ try {
|
||||
normalizedLiuBeiPerformance.damageDealt === 999999 &&
|
||||
normalizedLiuBeiPerformance.damageTaken === 0 &&
|
||||
normalizedLiuBeiPerformance.defeats === 2 &&
|
||||
normalizedLiuBeiPerformance.actions === 0 &&
|
||||
normalizedLiuBeiPerformance.support === 7 &&
|
||||
normalizedLiuBeiPerformance.actions === 0 &&
|
||||
normalizedLiuBeiPerformance.support === 7 &&
|
||||
normalizedLiuBeiPerformance.strategyUses === 2 &&
|
||||
normalizedLiuBeiPerformance.signatureStrategyUses === 2 &&
|
||||
normalizedLiuBeiPerformance.sortieBonusDamage === 3 &&
|
||||
normalizedLiuBeiPerformance.sortiePreventedDamage === 4 &&
|
||||
normalizedLiuBeiPerformance.sortieBonusHealing === 5 &&
|
||||
@@ -808,7 +846,9 @@ try {
|
||||
createdAt: '2026-07-03T09:00:00.000Z'
|
||||
};
|
||||
const performanceUnit = alliedFirstBattleUnits.find((unit) => unit.id === 'liu-bei') ?? alliedFirstBattleUnits[0];
|
||||
const recommendedButNotDeployedUnit = alliedFirstBattleUnits.find((unit) => unit.id !== performanceUnit?.id);
|
||||
assert(performanceUnit, 'Expected an allied first-battle unit for sortie performance clone checks');
|
||||
assert(recommendedButNotDeployedUnit, 'Expected a second allied unit for recommendation execution mismatch checks');
|
||||
const performanceReport = {
|
||||
...firstBattleReport,
|
||||
sortiePerformance: {
|
||||
@@ -824,6 +864,16 @@ try {
|
||||
enemyCount: 4,
|
||||
sourcePresetIds: ['elite', 'mobile']
|
||||
},
|
||||
sortieRecommendation: {
|
||||
version: 1,
|
||||
battleId: firstScenario.id,
|
||||
planId: 'order',
|
||||
label: '군령 달성형',
|
||||
summary: '추천 실행률의 부분 달성도 결과에서 평가합니다.',
|
||||
selectedUnitIds: [performanceUnit.id, recommendedButNotDeployedUnit.id],
|
||||
formationAssignments: { [performanceUnit.id]: 'front', [recommendedButNotDeployedUnit.id]: 'flank' },
|
||||
unitReasons: { [performanceUnit.id]: '전열을 맡습니다.', [recommendedButNotDeployedUnit.id]: '돌파를 맡습니다.' }
|
||||
},
|
||||
createdAt: '2026-07-03T09:05:00.000Z'
|
||||
};
|
||||
resetCampaignState();
|
||||
@@ -839,6 +889,8 @@ try {
|
||||
const clonedHistoryPerformance = clonedPerformanceSettlement.battleHistory[firstScenario.id]?.sortiePerformance;
|
||||
const clonedReportReview = clonedPerformanceSettlement.firstBattleReport?.sortieReview;
|
||||
const clonedHistoryReview = clonedPerformanceSettlement.battleHistory[firstScenario.id]?.sortieReview;
|
||||
const clonedReportRecommendation = clonedPerformanceSettlement.firstBattleReport?.sortieRecommendation;
|
||||
const clonedHistoryRecommendation = clonedPerformanceSettlement.battleHistory[firstScenario.id]?.sortieRecommendation;
|
||||
assert(
|
||||
JSON.stringify(clonedReportPerformance?.selectedUnitIds) === JSON.stringify([performanceUnit.id]) &&
|
||||
clonedReportPerformance.formationAssignments[performanceUnit.id] === 'front' &&
|
||||
@@ -851,8 +903,10 @@ try {
|
||||
JSON.stringify(clonedReportReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']) &&
|
||||
clonedHistoryReview?.score === 88 &&
|
||||
clonedHistoryReview.grade === 'A' &&
|
||||
JSON.stringify(clonedHistoryReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']),
|
||||
`Expected battle report settlement to deep-clone nested sortie performance and review input: ${JSON.stringify(clonedPerformanceSettlement)}`
|
||||
JSON.stringify(clonedHistoryReview.sourcePresetIds) === JSON.stringify(['elite', 'mobile']) &&
|
||||
JSON.stringify(clonedReportRecommendation?.selectedUnitIds) === JSON.stringify([performanceUnit.id, recommendedButNotDeployedUnit.id]) &&
|
||||
JSON.stringify(clonedHistoryRecommendation?.selectedUnitIds) === JSON.stringify([performanceUnit.id, recommendedButNotDeployedUnit.id]),
|
||||
`Expected battle report settlement to deep-clone performance/review data and preserve recommendation provenance for execution mismatch evaluation: ${JSON.stringify(clonedPerformanceSettlement)}`
|
||||
);
|
||||
clonedReportPerformance.selectedUnitIds.push('detached-unit');
|
||||
clonedReportPerformance.formationAssignments[performanceUnit.id] = 'reserve';
|
||||
|
||||
Reference in New Issue
Block a user