121 lines
5.8 KiB
JavaScript
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);
|
|
}
|