Files
heros_web/scripts/verify-first-victory-debrief-hierarchy.mjs

121 lines
5.8 KiB
JavaScript

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);
}