feat: improve early battle clarity and keyboard flow
This commit is contained in:
@@ -41,15 +41,22 @@ const server = await createServer({
|
||||
let battleScenarios;
|
||||
let scenarioModule;
|
||||
let storyAssetModule;
|
||||
let campaignFlowModule;
|
||||
let prologueMilitiaCampModule;
|
||||
|
||||
try {
|
||||
({ battleScenarios } = await server.ssrLoadModule('/src/game/data/battles.ts'));
|
||||
scenarioModule = await server.ssrLoadModule('/src/game/data/scenario.ts');
|
||||
storyAssetModule = await server.ssrLoadModule('/src/game/data/storyAssets.ts');
|
||||
campaignFlowModule = await server.ssrLoadModule('/src/game/data/campaignFlow.ts');
|
||||
prologueMilitiaCampModule = await server.ssrLoadModule(
|
||||
'/src/game/data/prologueMilitiaCamp.ts'
|
||||
);
|
||||
} finally {
|
||||
await server.close();
|
||||
}
|
||||
|
||||
validateEarlyHanSeokContinuity();
|
||||
validateCriticalVictoryObjectives();
|
||||
validateFinalStoryTransition();
|
||||
validateWuzhangStoryBackgrounds();
|
||||
@@ -66,11 +73,136 @@ if (failures.length > 0) {
|
||||
const criticalObjectiveCount = Object.values(criticalSecureObjectiveIds)
|
||||
.reduce((count, objectiveIds) => count + objectiveIds.length, 0);
|
||||
console.log(
|
||||
`Verified ${criticalObjectiveCount} narrative-critical secure objectives, ` +
|
||||
`Verified the Han Seok first-battle retreat, second-battle pursuit, and final defeat, ` +
|
||||
`${criticalObjectiveCount} narrative-critical secure objectives, ` +
|
||||
'the Wuzhang transition and background family, the 55-66 timeline chapter, ' +
|
||||
'and immediate unique camp reward continuity.'
|
||||
);
|
||||
|
||||
function validateEarlyHanSeokContinuity() {
|
||||
const firstBattleId = 'first-battle-zhuo-commandery';
|
||||
const secondBattleId = 'second-battle-yellow-turban-pursuit';
|
||||
const firstScenario = battleScenarios[firstBattleId];
|
||||
const secondScenario = battleScenarios[secondBattleId];
|
||||
|
||||
assert(firstScenario, `Missing first Han Seok encounter: ${firstBattleId}`);
|
||||
assert(secondScenario, `Missing Han Seok pursuit battle: ${secondBattleId}`);
|
||||
if (!firstScenario || !secondScenario) {
|
||||
return;
|
||||
}
|
||||
|
||||
const firstLeaderObjective = firstScenario.objectives.find(
|
||||
(objective) =>
|
||||
objective.kind === 'defeat-leader' &&
|
||||
objective.unitId === firstScenario.leaderUnitId
|
||||
);
|
||||
const firstBattleObjectiveCopy = [
|
||||
firstScenario.victoryConditionLabel,
|
||||
firstLeaderObjective?.label,
|
||||
...firstScenario.openingObjectiveLines
|
||||
].filter(Boolean).join(' ');
|
||||
assert(
|
||||
firstBattleObjectiveCopy.includes('한석') &&
|
||||
firstBattleObjectiveCopy.includes('선봉') &&
|
||||
/(격퇴|퇴각)/.test(firstBattleObjectiveCopy),
|
||||
`${firstBattleId}: the first Han Seok encounter must be framed as routing his vanguard`
|
||||
);
|
||||
assert(
|
||||
!/(?:한석|두령)[^.!?]{0,12}격파/.test(firstBattleObjectiveCopy),
|
||||
`${firstBattleId}: first-battle objective copy must not imply Han Seok is finally defeated`
|
||||
);
|
||||
|
||||
const commander = prologueMilitiaCampModule
|
||||
.prologueMilitiaCampNpcDefinitions
|
||||
.find((npc) => npc.id === 'zou-jing');
|
||||
const commanderText = commander?.dialogue.map((line) => line.text).join(' ') ?? '';
|
||||
assert(
|
||||
commanderText.includes('한석') && commanderText.includes('선봉'),
|
||||
'The pre-battle militia-camp command must introduce the first enemy as Han Seok’s vanguard'
|
||||
);
|
||||
|
||||
const departureText = scenarioModule.prologuePages
|
||||
.filter((page) => ['first-sortie', 'battle-briefing'].includes(page.id))
|
||||
.map((page) => page.text)
|
||||
.join(' ');
|
||||
assert(
|
||||
departureText.includes('한석') && departureText.includes('선봉'),
|
||||
'The playable prologue departure must preserve the Han Seok vanguard framing'
|
||||
);
|
||||
|
||||
const firstVictoryText = firstScenario.victoryPages
|
||||
.flatMap((page) => [
|
||||
page.text,
|
||||
page.cutscene?.title,
|
||||
page.cutscene?.subtitle,
|
||||
...(page.cutscene?.rewards?.map((reward) => reward.label) ?? [])
|
||||
])
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
assert(
|
||||
firstVictoryText.includes('한석') &&
|
||||
/(달아|퇴각|도주|빠져나)/.test(firstVictoryText) &&
|
||||
/(북쪽|나루)/.test(firstVictoryText) &&
|
||||
/(잔당|패잔병|남은 병력)/.test(firstVictoryText),
|
||||
`${firstBattleId}: victory story must explicitly show Han Seok escaping north with the remnants`
|
||||
);
|
||||
|
||||
const firstCampFlow = campaignFlowModule.getSortieFlow(firstBattleId);
|
||||
const firstCampBridge = [
|
||||
firstCampFlow?.title,
|
||||
firstCampFlow?.description,
|
||||
firstCampFlow?.rewardHint
|
||||
].filter(Boolean).join(' ');
|
||||
assert(
|
||||
firstCampFlow?.nextBattleId === secondBattleId &&
|
||||
firstCampBridge.includes('한석') &&
|
||||
/(퇴각|달아|도주)/.test(firstCampBridge) &&
|
||||
firstCampBridge.includes('추격'),
|
||||
'The first-camp sortie flow must bridge Han Seok’s retreat directly into the pursuit battle'
|
||||
);
|
||||
|
||||
const secondIntroText = scenarioModule.secondBattleIntroPages
|
||||
.flatMap((page) => [
|
||||
page.text,
|
||||
page.cutscene?.title,
|
||||
page.cutscene?.subtitle,
|
||||
...(page.cutscene?.rewards?.map((reward) => reward.label) ?? [])
|
||||
])
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
const secondBattleObjectiveCopy = [
|
||||
secondScenario.victoryConditionLabel,
|
||||
...secondScenario.openingObjectiveLines
|
||||
].join(' ');
|
||||
const secondVictoryText = secondScenario.victoryPages
|
||||
.flatMap((page) => [
|
||||
page.text,
|
||||
page.cutscene?.title,
|
||||
page.cutscene?.subtitle,
|
||||
...(page.cutscene?.rewards?.map((reward) => reward.label) ?? [])
|
||||
])
|
||||
.filter(Boolean)
|
||||
.join(' ');
|
||||
assert(
|
||||
secondIntroText.includes('탁현') &&
|
||||
secondIntroText.includes('한석') &&
|
||||
/(퇴각|달아|도주)/.test(secondIntroText) &&
|
||||
secondIntroText.includes('추격'),
|
||||
`${secondBattleId}: intro pages must identify the battle as pursuit of Han Seok after his Tak retreat`
|
||||
);
|
||||
assert(
|
||||
secondBattleObjectiveCopy.includes('탁현') &&
|
||||
secondBattleObjectiveCopy.includes('한석') &&
|
||||
secondBattleObjectiveCopy.includes('퇴각') &&
|
||||
secondScenario.victoryConditionLabel.includes('격파'),
|
||||
`${secondBattleId}: battle objective must close the explicitly linked Han Seok pursuit`
|
||||
);
|
||||
assert(
|
||||
secondVictoryText.includes('한석') && secondVictoryText.includes('격파'),
|
||||
`${secondBattleId}: victory story must explicitly close the pursuit with Han Seok’s final defeat`
|
||||
);
|
||||
}
|
||||
|
||||
function validateCriticalVictoryObjectives() {
|
||||
for (const [battleId, objectiveIds] of Object.entries(criticalSecureObjectiveIds)) {
|
||||
const scenario = battleScenarios[battleId];
|
||||
|
||||
Reference in New Issue
Block a user