import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; const battleSource = readFileSync('src/game/scenes/BattleScene.ts', 'utf8'); const storySource = readFileSync('src/game/scenes/StoryScene.ts', 'utf8'); const campSource = readFileSync('src/game/scenes/CampScene.ts', 'utf8'); const campaignFlowSource = readFileSync('src/game/data/campaignFlow.ts', 'utf8'); const battleResult = methodBlock(battleSource, 'private showBattleResult('); assert.match( battleResult, /renderResultMetric\('달성 목표', `\$\{achievedObjectiveCount\} \/ \$\{objectives\.length\}`/, 'Battle result should use the fourth headline metric for objective completion.' ); assert.doesNotMatch( battleResult, /renderResultMetric\('전투 보상'/, 'Battle result should not repeat the same gold total in its headline metrics and reward cards.' ); const battleSubtitle = methodBlock(battleSource, 'private resultSubtitle('); assert.match( battleSubtitle, /승리 후일담으로 이어집니다/, 'Victory subtitle should direct the player to the aftermath.' ); assert.doesNotMatch( battleSubtitle, /unlocks|recruits/, 'Victory subtitle must not reveal recruits or the next battlefield before the aftermath.' ); const battleRewards = methodBlock(battleSource, 'private renderResultRewardPanel('); assert.match( battleRewards, /신규 합류 \$\{rewards\.recruits\.length\}명 · 새 전장 \$\{rewards\.unlocks\.length\}곳/, 'Battle result should summarize future progression without naming story reveals.' ); assert.doesNotMatch( battleRewards, /rewards\.recruits\.map\([^)]*name|rewards\.unlocks\.map\([^)]*title/s, 'Battle result must not name recruits or unlocked battlefields before the aftermath.' ); const storyRewardDisplay = methodBlock(storySource, 'private rewardDisplayForPage('); assert.match(storyRewardDisplay, /source: 'campaign'/, 'First-victory aftermath should retain its campaign report link.'); assert.match(storyRewardDisplay, /battleId: report\.battleId/, 'First-victory aftermath should identify the source battle.'); assert.match(storyRewardDisplay, /rewardGold: null/, 'First-victory aftermath should not repeat exact gold.'); assert.match(storyRewardDisplay, /items: \[\]/, 'First-victory aftermath should not repeat system reward cards.'); assert.doesNotMatch( storyRewardDisplay, /campaignVictoryRewardPresentation|campaignRewards/, 'First-victory aftermath should not redistribute reward categories across dialogue pages.' ); assert.match( campSource, /this\.settleVictoryRewardArrival\(this\.pendingVictoryRewardReport\)/, 'Camp arrival should settle a pending victory without opening a blocking ledger.' ); assert.doesNotMatch( campSource, /delayedCall\(0,\s*\(\) => this\.showVictoryRewardAcknowledgement\(\)\)/, 'Camp arrival must not auto-open the reward ledger.' ); const campSettlement = methodBlock(campSource, 'private settleVictoryRewardArrival('); assert.match(campSettlement, /dismissCampaignVictoryRewardNotice/, 'Arrival should dismiss the one-time reward notice.'); assert.match(campSettlement, /category === 'gold' \|\| category === 'reputation'/, 'Arrival should acknowledge passive rewards.'); assert.match(campSettlement, /세부 획득 내역은 하단 전투 보고의 보상 장부/, 'Arrival should point to optional detail.'); assert.match(campSettlement, /pendingCategories:/, 'Arrival should preserve use-location NEW categories.'); const rewardLedger = methodBlock(campSource, 'private showVictoryRewardAcknowledgement('); assert.match(rewardLedger, /reportOverride\?: CampaignVictoryRewardReport/, 'Reward ledger should open for a chosen report.'); assert.match(rewardLedger, /BATTLE LEDGER/, 'Reward detail should be framed as an on-demand ledger.'); assert.match(rewardLedger, /\{ id: 'close', label: '장부 닫기', primary: true \}/, 'The ledger should have one clear primary close action.'); assert.doesNotMatch(rewardLedger, /id: 'guided'/, 'The detail ledger should not compete with the camp next-action guide.'); assert.doesNotMatch( rewardLedger, /playRewardRevealCue/, 'Reopening an archived reward ledger should not replay the victory reward celebration.' ); const reportPanel = methodBlock(campSource, 'private renderReportPanel('); assert.match(reportPanel, /showVictoryRewardAcknowledgement\(report\)/, 'Camp report should expose the detailed ledger on demand.'); assert.doesNotMatch(reportPanel, /report\.rewardGold|report\.itemRewards/, 'Always-visible camp report should not repeat exact rewards.'); assert.match(reportPanel, /세부 보상과 전과는 보상 장부에서 확인/, 'Always-visible camp report should explain where detail lives.'); const campReportSummary = methodBlock(campSource, 'private reportSummary('); assert.doesNotMatch( campReportSummary, /rewardGold|campaign\?\.gold|군자금/, 'Camp header should orient the player without repeating an exact reward or current gold total.' ); assert.match(campReportSummary, /군영 준비 중/, 'Camp header should return attention to the next preparation phase.'); assert.match( campSource, /role: 'on-demand-ledger'/, 'Debug state should expose the reward detail as an on-demand ledger.' ); assert.match( campSource, /victorySettlementArrival:[\s\S]*blocking: false/, 'Debug state should expose the camp arrival notice as non-blocking.' ); assert.doesNotMatch( campaignFlowSource, /지난 보상:/, 'Sortie preparation should describe the upcoming reward instead of repeating the previous battle reward.' ); console.log('Verified first-victory result, aftermath, camp, ledger, and sortie information hierarchy.'); function methodBlock(source, signature) { const start = source.indexOf(signature); assert.notEqual(start, -1, `Missing method signature: ${signature}`); const nextPrivate = source.indexOf('\n private ', start + signature.length); return source.slice(start, nextPrivate === -1 ? source.length : nextPrivate); }