import { createServer } from 'vite'; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true }, appType: 'custom' }); try { const { compareSortieSynergy, evaluateSortieSynergy, firstPursuitRolePreviewSummaries, firstPursuitSynergyBattleId, sortieBondBonusForLevel, sortieRoleEffectSummaries } = await server.ssrLoadModule('/src/game/data/sortieSynergy.ts'); const expectedRoleEffectSummaries = { front: '일반 공격 피해 -8%(최소 1) · 반격 +20%', flank: '초반 2턴 이동 +1 · 공격 피해 +10%', support: '공격 책략 +10% · 회복 +2' }; assert( JSON.stringify(sortieRoleEffectSummaries) === JSON.stringify(expectedRoleEffectSummaries), `Expected the three generic sortie role effects to retain their combat values and copy: ${JSON.stringify(sortieRoleEffectSummaries)}` ); assert( firstPursuitRolePreviewSummaries === sortieRoleEffectSummaries, 'Expected the first-pursuit role preview export to remain a compatibility alias.' ); const boundaryCases = [ [19, 0, 0], [20, 3, 0], [39, 3, 0], [40, 6, 0], [49, 6, 0], [50, 6, 8], [69, 9, 8], [70, 9, 18], [100, 15, 18] ]; boundaryCases.forEach(([level, damageBonus, chainRate]) => { const actual = sortieBondBonusForLevel(level); assert( actual.damageBonus === damageBonus && actual.chainRate === chainRate, `Unexpected Lv${level} bond bonus: ${JSON.stringify(actual)}` ); }); const members = [ { id: 'liu-bei', name: '유비', role: 'support', terrainScore: 101 }, { id: 'guan-yu', name: '관우', role: 'front', terrainScore: 105 }, { id: 'zhang-fei', name: '장비', role: 'flank', terrainScore: 98 }, { id: 'jian-yong', name: '간옹', role: 'support', terrainScore: 104 } ]; const bonds = [ { id: 'oath', title: '도원결의', unitIds: ['liu-bei', 'guan-yu'], level: 72, exp: 4 }, { id: 'brothers', title: '의형제', unitIds: ['guan-yu', 'zhang-fei'], level: 68, exp: 3 }, { id: 'vow', title: '맹세', unitIds: ['liu-bei', 'zhang-fei'], level: 64, exp: 2 }, { id: 'records', title: '군영 기록', unitIds: ['liu-bei', 'jian-yong'], level: 51, exp: 1 }, { id: 'self', title: '잘못된 관계', unitIds: ['liu-bei', 'liu-bei'], level: 99, exp: 0 }, { id: 'unknown', title: '알 수 없음', unitIds: ['liu-bei', 'ghost'], level: 99, exp: 0 } ]; const frozenInputs = JSON.stringify({ members, bonds }); const trio = evaluateSortieSynergy({ members, bonds, selectedUnitIds: ['liu-bei', 'guan-yu', 'zhang-fei', 'guan-yu'], battleId: firstPursuitSynergyBattleId }); assert(trio.selectedUnitIds.length === 3, `Expected duplicate selections to collapse: ${JSON.stringify(trio.selectedUnitIds)}`); assert(trio.activeBondCount === 3, `Expected three active brother bonds: ${JSON.stringify(trio.activeBonds)}`); assert(trio.strongestBond?.id === 'oath', `Expected Lv72 oath to be strongest: ${JSON.stringify(trio.strongestBond)}`); assert(trio.strongestBond?.damageBonus === 9 && trio.strongestBond?.chainRate === 18, 'Expected strongest combat preview to match BattleScene rules.'); assert(trio.coveredRoleCount === 3 && trio.firstPursuitTrinityConfigured, `Expected full role coverage and trinity: ${JSON.stringify(trio)}`); const withoutGuan = evaluateSortieSynergy({ members, bonds, selectedUnitIds: ['liu-bei', 'zhang-fei'], battleId: firstPursuitSynergyBattleId }); assert(withoutGuan.activeBondCount === 1 && withoutGuan.activeBonds[0]?.id === 'vow', `Expected only the Liu-Zhang bond after Guan Yu leaves: ${JSON.stringify(withoutGuan.activeBonds)}`); assert(withoutGuan.coveredRoleCount === 2 && !withoutGuan.firstPursuitTrinityConfigured, 'Expected removing Guan Yu to break role coverage and trinity.'); const removal = compareSortieSynergy(trio, withoutGuan); assert(removal.activeBondDelta === -2 && removal.lostBonds.length === 2, `Expected two lost bonds: ${JSON.stringify(removal)}`); assert(removal.roleCoverageDelta === -1 && removal.lostRoles[0] === 'front', `Expected the front role to be lost: ${JSON.stringify(removal.lostRoles)}`); const addition = compareSortieSynergy(withoutGuan, trio); assert(addition.activeBondDelta === 2 && addition.gainedBonds.length === 2, `Expected adding Guan Yu to restore two bonds: ${JSON.stringify(addition)}`); assert(addition.roleCoverageDelta === 1 && addition.gainedRoles[0] === 'front', `Expected adding Guan Yu to restore the front role: ${JSON.stringify(addition.gainedRoles)}`); const restoredRoles = evaluateSortieSynergy({ members: members.map((member) => member.id === 'guan-yu' ? { ...member, role: 'support' } : member), bonds, selectedUnitIds: ['liu-bei', 'guan-yu', 'zhang-fei'], battleId: firstPursuitSynergyBattleId }); assert(restoredRoles.activeBondCount === 3, 'Expected role changes not to alter active bonds.'); assert(restoredRoles.coveredRoleCount === 2 && !restoredRoles.firstPursuitTrinityConfigured, 'Expected duplicate support roles to disable trinity.'); const reordered = evaluateSortieSynergy({ members: [...members].reverse(), bonds: [...bonds].reverse(), selectedUnitIds: ['zhang-fei', 'guan-yu', 'liu-bei'], battleId: firstPursuitSynergyBattleId }); assert( reordered.activeBonds.map((bond) => bond.id).join(',') === trio.activeBonds.map((bond) => bond.id).join(','), `Expected deterministic bond ordering: ${JSON.stringify(reordered.activeBonds)}` ); assert(JSON.stringify({ members, bonds }) === frozenInputs, 'Expected synergy evaluation not to mutate its inputs.'); console.log('Verified generic sortie role effects, synergy previews, bond thresholds, role coverage, and first-pursuit trinity.'); } finally { await server.close(); } function assert(condition, message) { if (!condition) { throw new Error(message); } }