feat: surface officer strategies in sortie planning
This commit is contained in:
@@ -3862,6 +3862,26 @@ try {
|
||||
midFormationPortraitState?.sortiePortraitRoster?.length === 23 && missingMidFormationPortraits.length === 0,
|
||||
`Expected all 23 campaign officers to use loaded lightweight portrait cards: ${JSON.stringify(missingMidFormationPortraits)}`
|
||||
);
|
||||
const midPortraitsMissingStrategyCopy = midFormationPortraitState?.sortiePortraitRoster?.filter(
|
||||
(unit) => !Array.isArray(unit.strategyNames) || unit.strategyNames.length === 0 || !unit.strategyLine?.startsWith('전법 ')
|
||||
) ?? [];
|
||||
assert(
|
||||
midPortraitsMissingStrategyCopy.length === 0 &&
|
||||
sameJsonValue(midFormationPortraitState?.sortiePortraitRoster?.find((unit) => unit.id === 'guan-yu')?.strategyNames, ['화계']) &&
|
||||
sameJsonValue(midFormationPortraitState?.sortiePortraitRoster?.find((unit) => unit.id === 'zhang-fei')?.strategyNames, ['고함']),
|
||||
`Expected every sortie portrait card to expose its battle strategies: ${JSON.stringify(midPortraitsMissingStrategyCopy)}`
|
||||
);
|
||||
assert(
|
||||
midFormationPortraitState?.sortieStrategyCoverage?.count === 4 &&
|
||||
midFormationPortraitState.sortieStrategyCoverage.total === 4 &&
|
||||
midFormationPortraitState.sortieStrategyCoverage.missing.length === 0 &&
|
||||
sameJsonValue(midFormationPortraitState.sortieStrategyCoverage.covered.map((definition) => definition.label), ['회복', '강화', '화공', '제어']) &&
|
||||
midFormationPortraitState?.sortieFocusedUnit?.strategyLine?.startsWith('전법 '),
|
||||
`Expected the selected formation and focused officer to expose complete strategy coverage: ${JSON.stringify({
|
||||
coverage: midFormationPortraitState?.sortieStrategyCoverage,
|
||||
focused: midFormationPortraitState?.sortieFocusedUnit
|
||||
})}`
|
||||
);
|
||||
|
||||
const midCoreSelectorToggle = midFormationPortraitState?.sortiePursuitPreview;
|
||||
assert(
|
||||
@@ -4042,15 +4062,25 @@ try {
|
||||
swapPreview?.comparison?.lostRoles?.includes('flank') &&
|
||||
swapPreview?.comparison?.activeBondDelta === 0 &&
|
||||
swapPreview?.comparison?.gainedBonds?.length > 0 &&
|
||||
swapPreview?.comparison?.lostBonds?.length > 0,
|
||||
swapPreview?.comparison?.lostBonds?.length > 0 &&
|
||||
swapPreview?.strategyCoverage?.current?.count === 4 &&
|
||||
swapPreview?.strategyCoverage?.projected?.count === 4 &&
|
||||
swapPreview?.strategyCoverage?.current?.total === 4 &&
|
||||
swapPreview?.strategyCoverage?.projected?.total === 4 &&
|
||||
swapPreview?.strategyCoverage?.delta === 0 &&
|
||||
swapPreview?.strategyCoverage?.trend === 'even' &&
|
||||
swapPreview?.strategyCoverage?.gained?.length === 0 &&
|
||||
swapPreview?.strategyCoverage?.lost?.length === 0,
|
||||
`Expected a meaningful non-destructive Ma Dai to Guan Yu swap preview: ${JSON.stringify(swapPreview)}`
|
||||
);
|
||||
assert(
|
||||
midFormationSwapPreviewProbe.panel.title === '가상 교체 · 마대 → 관우' &&
|
||||
midFormationSwapPreviewProbe.panel.headline?.includes('OUT 마대') &&
|
||||
midFormationSwapPreviewProbe.panel.headline?.includes('IN 관우') &&
|
||||
midFormationSwapPreviewProbe.panel.impact?.includes('전법 4/4→4/4') &&
|
||||
midFormationSwapPreviewProbe.panel.impact?.includes('추격') &&
|
||||
midFormationSwapPreviewProbe.panel.impact?.includes('공백 돌파') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('전법 유지') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('추격 해제') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('8%') &&
|
||||
midFormationSwapPreviewProbe.panel.detail?.includes('실제 편성 미변경'),
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user