import assert from 'node:assert/strict'; import { createServer } from 'vite'; const server = await createServer({ logLevel: 'error', server: { middlewareMode: true, hmr: false }, appType: 'custom' }); try { const dialogueModule = await server.ssrLoadModule( '/src/game/data/thirdBattleReturnDialogue.ts' ); const { battleScenarios } = await server.ssrLoadModule( '/src/game/data/battles.ts' ); verifyCanonicalBattleReferences(dialogueModule, battleScenarios); verifyAllObjectiveCombinations(dialogueModule); verifyTargetBattleIsolation(dialogueModule); verifyLegacyReportFallback(dialogueModule); verifyInvalidLegacyDataFallsBackToStaticDialogue(dialogueModule); verifyReturnedDataIsCloned(dialogueModule); console.log( 'Third-battle priority return dialogue verification passed (four objective combinations, result-specific priority targets, exact battle isolation, and static-dialogue fallback for invalid legacy data).' ); } finally { await server.close(); } function verifyCanonicalBattleReferences(dialogueModule, battleScenarios) { const scenario = battleScenarios[dialogueModule.thirdBattleReturnSourceBattleId]; assert(scenario, 'The return dialogue must reference a canonical battle.'); assert.equal(scenario.id, 'third-battle-guangzong-road'); assert.equal( scenario.quickVictoryTurnLimit, dialogueModule.thirdBattleReturnQuickVictoryTurnLimit ); const fortObjective = scenario.objectives.find( ({ id }) => id === dialogueModule.thirdBattleReturnObjectiveIds.fort ); const quickObjective = scenario.objectives.find( ({ id }) => id === dialogueModule.thirdBattleReturnObjectiveIds.quick ); assert.equal(fortObjective?.kind, 'secure-terrain'); assert.equal(fortObjective?.label, '강가 요새 확보'); assert.equal(quickObjective?.kind, 'quick-victory'); assert.equal( quickObjective?.maxTurn, dialogueModule.thirdBattleReturnQuickVictoryTurnLimit ); assert.deepEqual( dialogueModule.thirdBattleReturnTargetDialogueIds, { fortRecovery: 'liu-guan-after-guangzong-road', tempoRecovery: 'liu-zhang-after-guangzong-road', decisiveAdvance: 'guan-zhang-after-guangzong-road' } ); assert.equal( dialogueModule.thirdBattleReturnPriorityLabel, '새 이야기 · 우선' ); } function verifyAllObjectiveCombinations(dialogueModule) { const testCases = [ { fortAchieved: false, quickAchieved: false, turnNumber: 35, expectedVariantId: 'fort-recovery', expectedTargetDialogueId: 'liu-guan-after-guangzong-road', expectedSummary: /강가 요새 미확보 · 35턴 · 30턴 제한 초과/, expectedLine: /35턴 만에/, forbiddenText: /강가 요새까지 확보했습니다/ }, { fortAchieved: false, quickAchieved: true, turnNumber: 24, expectedVariantId: 'fort-recovery', expectedTargetDialogueId: 'liu-guan-after-guangzong-road', expectedSummary: /강가 요새 미확보 · 24턴 신속 승리/, expectedLine: /전령을 쫓는 속도는 늦지 않았지만/, forbiddenText: /강가 요새까지 확보했습니다/ }, { fortAchieved: true, quickAchieved: false, turnNumber: 34, expectedVariantId: 'tempo-recovery', expectedTargetDialogueId: 'liu-zhang-after-guangzong-road', expectedSummary: /강가 요새 확보 · 34턴 · 30턴 제한 초과/, expectedLine: /34턴이 걸려/, forbiddenText: /신속 승리/ }, { fortAchieved: true, quickAchieved: true, turnNumber: 22, expectedVariantId: 'decisive-advance', expectedTargetDialogueId: 'guan-zhang-after-guangzong-road', expectedSummary: /강가 요새 확보 · 22턴 신속 승리/, expectedLine: /22턴 만에/, forbiddenText: /확보하지 못했습니다/ } ]; testCases.forEach((testCase) => { const resolved = dialogueModule.resolveThirdBattlePriorityReturnDialogue( campaignWithHistory(dialogueModule, testCase) ); assert(resolved, `Expected a dialogue for ${JSON.stringify(testCase)}.`); assert.equal(resolved.reportSource, 'battle-history'); assert.equal( resolved.sourceBattleId, dialogueModule.thirdBattleReturnSourceBattleId ); assert.equal( resolved.targetDialogueId, testCase.expectedTargetDialogueId ); assert.equal(resolved.priorityLabel, '새 이야기 · 우선'); assert.equal(resolved.variantId, testCase.expectedVariantId); assert.equal(resolved.turnNumber, testCase.turnNumber); assert.equal(resolved.fortAchieved, testCase.fortAchieved); assert.equal(resolved.quickAchieved, testCase.quickAchieved); assert.equal(resolved.lines.length, 3); assert.match(resolved.summary, testCase.expectedSummary); assert.match(resolved.lines.join('\n'), testCase.expectedLine); assert.doesNotMatch( `${resolved.summary}\n${resolved.lines.join('\n')}`, testCase.forbiddenText, 'A missed objective must never receive false praise.' ); }); const failedStatusOverridesAchieved = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: victoryReport( dialogueModule, { fortAchieved: true, quickAchieved: false, turnNumber: 35, fortStatus: 'failed' } ) } }); assert.equal( failedStatusOverridesAchieved?.fortAchieved, false, 'A failed objective status must prevent phantom achievement praise.' ); assert.equal( failedStatusOverridesAchieved?.variantId, 'fort-recovery' ); } function verifyTargetBattleIsolation(dialogueModule) { const targetVictory = victoryReport(dialogueModule, { fortAchieved: true, quickAchieved: true, turnNumber: 20 }); const otherVictory = { ...targetVictory, battleId: 'fourth-battle-guangzong-camp' }; assert.equal( dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { 'fourth-battle-guangzong-camp': otherVictory }, firstBattleReport: otherVictory }), undefined, 'An unrelated victory must not create a third-camp priority dialogue.' ); assert.equal( dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: otherVictory }, firstBattleReport: targetVictory }), undefined, 'A mismatched canonical history entry must block stale report fallback.' ); assert.equal( dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: { ...targetVictory, outcome: 'defeat' } }, firstBattleReport: targetVictory }), undefined, 'A canonical defeat must not be replaced by a stale victory report.' ); assert.equal( dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { 'legacy-wrong-key': targetVictory } }), undefined, 'A target settlement stored under another battle key must be ignored.' ); const historyWins = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: targetVictory }, firstBattleReport: otherVictory }); assert.equal(historyWins?.reportSource, 'battle-history'); assert.equal(historyWins?.variantId, 'decisive-advance'); } function verifyLegacyReportFallback(dialogueModule) { const legacyReport = victoryReport(dialogueModule, { fortAchieved: true, quickAchieved: true, turnNumber: 19, omitStatus: true }); const fromMissingHistory = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ firstBattleReport: legacyReport }); assert.equal(fromMissingHistory?.reportSource, 'legacy-report'); assert.equal(fromMissingHistory?.variantId, 'decisive-advance'); assert.equal(fromMissingHistory?.fortAchieved, true); assert.equal(fromMissingHistory?.quickAchieved, true); assert.match(fromMissingHistory?.lines.join('\n') ?? '', /19턴 만에/); const fromEmptyHistory = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: {}, firstBattleReport: legacyReport }); assert.equal(fromEmptyHistory?.reportSource, 'legacy-report'); assert.equal(fromEmptyHistory?.variantId, 'decisive-advance'); const missingTurn = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ firstBattleReport: { ...legacyReport, turnNumber: 'unknown' } }); assert.equal( missingTurn, undefined, 'A legacy report without a trustworthy turn must preserve the static dialogue.' ); } function verifyInvalidLegacyDataFallsBackToStaticDialogue( dialogueModule ) { const complete = victoryReport(dialogueModule, { fortAchieved: true, quickAchieved: true, turnNumber: 21 }); const missingObjective = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ firstBattleReport: { ...complete, objectives: complete.objectives.filter( ({ id }) => id !== dialogueModule.thirdBattleReturnObjectiveIds.quick ) } }); assertNoPriorityOverride( missingObjective, 'A legacy report with a missing objective' ); const duplicateObjective = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: { ...complete, objectives: [ ...complete.objectives, { id: dialogueModule.thirdBattleReturnObjectiveIds.fort, achieved: false, status: 'failed' } ] } } }); assertNoPriorityOverride( duplicateObjective, 'A report with duplicate objective ids' ); const invalidObjective = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ firstBattleReport: { ...complete, objectives: complete.objectives.map((objective) => objective.id === dialogueModule.thirdBattleReturnObjectiveIds.fort ? { ...objective, achieved: 'yes' } : objective ) } }); assertNoPriorityOverride( invalidObjective, 'A legacy report with a non-boolean objective result' ); const inconsistentTurn = dialogueModule.resolveThirdBattlePriorityReturnDialogue({ firstBattleReport: victoryReport(dialogueModule, { fortAchieved: true, quickAchieved: true, turnNumber: 31 }) }); assertNoPriorityOverride( inconsistentTurn, 'A quick-victory result that contradicts its turn number' ); } function verifyReturnedDataIsCloned(dialogueModule) { const campaign = campaignWithHistory(dialogueModule, { fortAchieved: true, quickAchieved: true, turnNumber: 18 }); const first = dialogueModule.resolveThirdBattlePriorityReturnDialogue(campaign); assert(first); first.lines[0] = 'mutated'; const second = dialogueModule.resolveThirdBattlePriorityReturnDialogue(campaign); assert(second); assert.notEqual(second.lines[0], 'mutated'); } function campaignWithHistory(dialogueModule, options) { return { battleHistory: { [dialogueModule.thirdBattleReturnSourceBattleId]: victoryReport(dialogueModule, options) }, firstBattleReport: { ...victoryReport(dialogueModule, options), battleId: 'fourth-battle-guangzong-camp' } }; } function victoryReport( dialogueModule, { fortAchieved, quickAchieved, turnNumber, fortStatus, quickStatus, omitStatus = false } ) { const objective = (id, achieved, status) => ({ id, achieved, ...(omitStatus ? {} : { status: status ?? (achieved ? 'done' : 'failed') }) }); return { battleId: dialogueModule.thirdBattleReturnSourceBattleId, outcome: 'victory', turnNumber, objectives: [ objective( dialogueModule.thirdBattleReturnObjectiveIds.fort, fortAchieved, fortStatus ), objective( dialogueModule.thirdBattleReturnObjectiveIds.quick, quickAchieved, quickStatus ) ] }; } function assertNoPriorityOverride(resolved, condition) { assert.equal( resolved, undefined, `${condition} must leave the existing static dialogue unchanged.` ); }