feat: surface officer strategies in sortie planning

This commit is contained in:
2026-07-12 12:28:51 +09:00
parent 3daad2861b
commit cb66deef9d
5 changed files with 395 additions and 162 deletions

View File

@@ -25,6 +25,60 @@ try {
sortieOrderRewardClaimId,
summarizeSortieOrderHistory
} = await server.ssrLoadModule('/src/game/data/sortieOrders.ts');
const {
compareUnitStrategyCoverage,
getUnitStrategyCoverage,
getUnitStrategyNames,
strategyCoverageDefinitions,
unitStrategyIds,
usableCatalog
} = await server.ssrLoadModule('/src/game/data/battleUsables.ts');
assert(
JSON.stringify(strategyCoverageDefinitions.map(({ id, label, usableId }) => ({ id, label, usableId }))) === JSON.stringify([
{ id: 'healing', label: '회복', usableId: 'aid' },
{ id: 'buff', label: '강화', usableId: 'encourage' },
{ id: 'fire', label: '화공', usableId: 'fireTactic' },
{ id: 'control', label: '제어', usableId: 'roar' }
]),
`Expected the four sortie strategy coverage roles to remain stable: ${JSON.stringify(strategyCoverageDefinitions)}`
);
assert(
JSON.stringify(getUnitStrategyNames(['liu-bei'])) === JSON.stringify(['응급', '격려']),
`Expected Liu Bei's strategy names to be visible before sortie: ${JSON.stringify(getUnitStrategyNames(['liu-bei']))}`
);
const brotherStrategyCoverage = getUnitStrategyCoverage(['liu-bei', 'guan-yu', 'zhang-fei', 'liu-bei']);
assert(
brotherStrategyCoverage.count === 4 &&
brotherStrategyCoverage.total === 4 &&
brotherStrategyCoverage.missing.length === 0,
`Expected the three brothers to cover healing, buff, attack, and control: ${JSON.stringify(brotherStrategyCoverage)}`
);
const noFireStrategyCoverage = getUnitStrategyCoverage(['liu-bei', 'zhang-fei', 'unknown-unit']);
assert(
noFireStrategyCoverage.count === 3 &&
noFireStrategyCoverage.missing.length === 1 &&
noFireStrategyCoverage.missing[0]?.id === 'fire',
`Expected removing Guan Yu to expose the missing fire-tactic role: ${JSON.stringify(noFireStrategyCoverage)}`
);
const evenStrategyTrade = compareUnitStrategyCoverage(['liu-bei', 'guan-yu'], ['liu-bei', 'zhang-fei']);
assert(
evenStrategyTrade.delta === 0 &&
evenStrategyTrade.trend === 'even' &&
evenStrategyTrade.gained.length === 1 &&
evenStrategyTrade.gained[0]?.id === 'control' &&
evenStrategyTrade.lost.length === 1 &&
evenStrategyTrade.lost[0]?.id === 'fire',
`Expected exchanging fire tactics for control at equal coverage to remain a neutral trade: ${JSON.stringify(evenStrategyTrade)}`
);
const unknownStrategyIds = Object.entries(unitStrategyIds)
.flatMap(([unitId, strategyIds]) => strategyIds.map((strategyId) => ({ unitId, strategyId })))
.filter(({ strategyId }) => usableCatalog[strategyId]?.command !== 'strategy');
assert(unknownStrategyIds.length === 0, `Expected every officer strategy id to resolve to a battle strategy: ${JSON.stringify(unknownStrategyIds)}`);
assert(
['bean', 'salve', 'wine'].every((itemId) => usableCatalog[itemId]?.command === 'item'),
'Expected the shared usable catalog to retain all three battle items.'
);
const expectedRoleEffectSummaries = {
front: '일반 공격 피해 -8%(최소 1) · 반격 +20%',
@@ -433,7 +487,7 @@ try {
);
assert(JSON.stringify(orderPerformance) === frozenOrderPerformance, 'Expected sortie-order evaluation not to mutate performance input.');
console.log('Verified generic sortie role effects, synergy previews, bond thresholds, role coverage, first-pursuit trinity, and sortie-order evaluation/reward/history boundaries.');
console.log('Verified officer strategy coverage, generic sortie role effects, synergy previews, bond thresholds, role coverage, first-pursuit trinity, and sortie-order evaluation/reward/history boundaries.');
} finally {
await server.close();
}