feat: add live sortie order HUD

This commit is contained in:
2026-07-11 01:51:36 +09:00
parent 23bd55735b
commit 9f49069db2
4 changed files with 633 additions and 10 deletions

View File

@@ -17,6 +17,7 @@ try {
} = await server.ssrLoadModule('/src/game/data/sortieSynergy.ts');
const {
evaluateSortieOrder,
evaluateSortieOrderProgress,
sortieOrderDefinitions,
sortieOrderRewardClaimId,
summarizeSortieOrderHistory
@@ -229,6 +230,65 @@ try {
`Expected siege order to prefer terrain objectives and require front/support contributions: ${JSON.stringify({ siegeBoundary, siegeBonusFallback, siegeTerrainPrecedence, siegeWithoutFront, siegeWithoutSupport })}`
);
const siegeExtendedSupportInput = orderInput({
performance: {
...orderPerformance,
unitStats: orderPerformance.unitStats.map((stats) => stats.unitId === 'support-unit'
? {
...stats,
sortieBonusDamage: 0,
sortieBonusHealing: 0,
sortieExtendedBondTriggers: 1
}
: { ...stats })
}
});
const progressBoundaryCases = [
['elite exact grade', 'elite', orderInput()],
['elite below grade', 'elite', orderInput({ score: 74 })],
['elite empty deployment', 'elite', orderInput({ survivorCount: 0, deployedCount: 0 })],
['mobile exact turn', 'mobile', orderInput()],
['mobile late turn', 'mobile', orderInput({ turnNumber: 13 })],
['mobile invalid zero limit', 'mobile', orderInput({ turnNumber: 0, turnLimit: 0 })],
['mobile intent only', 'mobile', orderInput({ performance: intentOnlyPerformance })],
['siege terrain boundary', 'siege', orderInput()],
['siege terrain precedence', 'siege', orderInput({
terrainObjectiveTotal: 1,
terrainObjectiveAchieved: 0,
bonusObjectiveAchieved: 2
})],
['siege bonus fallback', 'siege', orderInput({
terrainObjectiveTotal: 0,
terrainObjectiveAchieved: 0,
bonusObjectiveAchieved: 1
})],
['siege extended support only', 'siege', siegeExtendedSupportInput]
];
progressBoundaryCases.forEach(([label, orderId, input]) => {
const progressEvaluation = evaluateSortieOrderProgress(orderId, input);
const finalEvaluation = evaluateSortieOrder(orderId, input);
const finalProgressEvaluation = {
orderId: finalEvaluation.orderId,
score: finalEvaluation.score,
achieved: finalEvaluation.achieved,
progress: finalEvaluation.progress
};
assert(
JSON.stringify(progressEvaluation) === JSON.stringify(finalProgressEvaluation),
`Expected public progress helper and final evaluator parity at ${label}: ${JSON.stringify({ progressEvaluation, finalProgressEvaluation })}`
);
});
const emptyEliteProgress = evaluateSortieOrderProgress('elite', orderInput({ survivorCount: 0, deployedCount: 0 }));
const zeroLimitMobileProgress = evaluateSortieOrderProgress('mobile', orderInput({ turnNumber: 0, turnLimit: 0 }));
const extendedSupportSiegeProgress = evaluateSortieOrderProgress('siege', siegeExtendedSupportInput);
assert(
emptyEliteProgress.progress.find((entry) => entry.id === 'elite-survival')?.achieved === false &&
zeroLimitMobileProgress.progress.find((entry) => entry.id === 'mobile-quick')?.achieved === false &&
extendedSupportSiegeProgress.progress.find((entry) => entry.id === 'siege-support')?.achieved === true,
`Expected progress helper to retain empty-deployment, zero-limit, and extended-support boundaries: ${JSON.stringify({ emptyEliteProgress, zeroLimitMobileProgress, extendedSupportSiegeProgress })}`
);
const rewardByOrder = Object.fromEntries(sortieOrderDefinitions.map((definition) => [definition.id, definition]));
assert(
rewardByOrder.elite?.rewardGold === 180 && JSON.stringify(rewardByOrder.elite.rewardItems) === JSON.stringify(['상처약 1']) &&