feat: align campaign audiovisual story flow
This commit is contained in:
@@ -4,6 +4,9 @@ const campaignStateSource = readFileSync('src/game/state/campaignState.ts', 'utf
|
||||
const campaignRoutingSource = readFileSync('src/game/state/campaignRouting.ts', 'utf8');
|
||||
const campaignFlowSource = readFileSync('src/game/data/campaignFlow.ts', 'utf8');
|
||||
const battlesSource = readFileSync('src/game/data/battles.ts', 'utf8');
|
||||
const battleSceneSource = readFileSync('src/game/scenes/BattleScene.ts', 'utf8');
|
||||
const campSceneSource = readFileSync('src/game/scenes/CampScene.ts', 'utf8');
|
||||
const titleSceneSource = readFileSync('src/game/scenes/TitleScene.ts', 'utf8');
|
||||
|
||||
const battleScenarioIds = extractBattleScenarioIds(battlesSource);
|
||||
const battleIds = new Set(battleScenarioIds.values());
|
||||
@@ -64,6 +67,10 @@ for (const flowKey of sortieFlowKeys) {
|
||||
}
|
||||
}
|
||||
assert(/\bpages\s*:/.test(flowBody), `Sortie flow ${flowKey} must provide story pages`);
|
||||
assert(
|
||||
!/\b\w+BattleVictoryPages\b/.test(flowBody),
|
||||
`Sortie flow ${flowKey} must not defer a completed battle's victory story until the next camp sortie`
|
||||
);
|
||||
for (const fieldName of sortieFlowDisplayFields) {
|
||||
assert(hasVisibleSortieFlowField(flowBody, fieldName), `Sortie flow ${flowKey} must provide a visible ${fieldName}`);
|
||||
}
|
||||
@@ -72,6 +79,7 @@ for (const flowKey of sortieFlowKeys) {
|
||||
for (const flowKey of sortieFlowKeys) {
|
||||
const nextBattleId = sortieFlowNextBattleIds.get(flowKey);
|
||||
const campaignStep = sortieFlowCampaignSteps.get(flowKey);
|
||||
const flowBody = sortieFlowBodies.get(flowKey) ?? '';
|
||||
if (!nextBattleId) {
|
||||
continue;
|
||||
}
|
||||
@@ -83,6 +91,10 @@ for (const flowKey of sortieFlowKeys) {
|
||||
`Sortie flow ${flowKey} marks campaign step ${campaignStep} but routes to ${routedBattleSteps.get(campaignStep) ?? 'no battle'} instead of ${nextBattleId}`
|
||||
);
|
||||
}
|
||||
assert(
|
||||
/\bpages\s*:\s*\w+BattleIntroPages\b/.test(flowBody),
|
||||
`Battle sortie flow ${flowKey} must contain only the next battle intro pages`
|
||||
);
|
||||
}
|
||||
|
||||
for (const flowKey of sortieFlowKeys) {
|
||||
@@ -122,6 +134,54 @@ for (const step of campaignSteps) {
|
||||
}
|
||||
}
|
||||
|
||||
const battleVictoryStoryRouteCount = countOccurrences(battleSceneSource, 'pages: battleScenario.victoryPages');
|
||||
const campSceneDestinationCount = countOccurrences(battlesSource, "nextCampScene: 'CampScene'") - 1;
|
||||
assert(
|
||||
campSceneDestinationCount === battleIds.size,
|
||||
`Every battle scenario must return to the typed CampScene destination after its aftermath (found ${campSceneDestinationCount}/${battleIds.size})`
|
||||
);
|
||||
assert(
|
||||
battleVictoryStoryRouteCount === 2,
|
||||
`Battle results and victory improvement navigation must both route through the completed scenario's victory pages (found ${battleVictoryStoryRouteCount})`
|
||||
);
|
||||
assert(
|
||||
battleSceneSource.includes("return { destination: 'victory-story' as const, label: '승리 이야기로' }"),
|
||||
'Every completed battle result must advertise the victory-story destination before camp'
|
||||
);
|
||||
assert(
|
||||
/private resultContinueRoute\(outcome = this\.battleOutcome\)\s*\{\s*if \(outcome !== 'victory'\) \{\s*return undefined;/.test(battleSceneSource),
|
||||
'Defeat results must never expose the victory-story continuation route'
|
||||
);
|
||||
assert(
|
||||
/if \(outcome === 'victory'\) \{\s*this\.resultContinueButton = this\.addResultButton/.test(battleSceneSource),
|
||||
'The victory-story result CTA must only be created for victories'
|
||||
);
|
||||
assert(
|
||||
/if \(outcome === 'victory'\) \{[\s\S]*?pages: battleScenario\.victoryPages,[\s\S]*?return;\s*\}\s*this\.beginResultNavigation\(\(\) => startLazyScene\(this, 'CampScene', campSceneData\)\);/.test(battleSceneSource),
|
||||
'Victory improvement navigation must show aftermath pages, while defeat improvement navigation must return directly to camp for retry'
|
||||
);
|
||||
assert(
|
||||
!battleSceneSource.includes('firstBattleVictoryPages'),
|
||||
'BattleScene must not special-case first-battle aftermath pages'
|
||||
);
|
||||
assert(
|
||||
countOccurrences(battleSceneSource, 'completedAftermathBattleId: battleScenario.id') === 2,
|
||||
'Both victory result routes must clear their persisted aftermath marker only when CampScene is reached'
|
||||
);
|
||||
assert(
|
||||
campaignStateSource.includes('state.pendingAftermathBattleId = battleId as BattleScenarioId') &&
|
||||
campaignStateSource.includes('export function completeCampaignAftermath'),
|
||||
'Campaign saves must persist a pending victory aftermath and expose an explicit completion transition'
|
||||
);
|
||||
assert(
|
||||
/if \(campaign\.pendingAftermathBattleId\)[\s\S]*?pages: aftermathScenario\.victoryPages,[\s\S]*?presentationStage: 'aftermath'[\s\S]*?if \(campaign\.step === 'ending-complete'\)/.test(titleSceneSource),
|
||||
'Continue must restore a pending aftermath before routing camp or ending campaign steps'
|
||||
);
|
||||
assert(
|
||||
/if \(data\?\.completedAftermathBattleId\) \{\s*completeCampaignAftermath\(data\.completedAftermathBattleId\);\s*\}/.test(campSceneSource),
|
||||
'CampScene must complete the pending aftermath before loading its campaign snapshot'
|
||||
);
|
||||
|
||||
if (failures.length > 0) {
|
||||
throw new Error(`Campaign flow data verification failed:\n${failures.map((failure) => `- ${failure}`).join('\n')}`);
|
||||
}
|
||||
@@ -136,6 +196,10 @@ function assert(condition, message) {
|
||||
}
|
||||
}
|
||||
|
||||
function countOccurrences(source, value) {
|
||||
return source.split(value).length - 1;
|
||||
}
|
||||
|
||||
function extractBattleScenarioIds(source) {
|
||||
const ids = new Map();
|
||||
const declarationPattern = /export const (\w+BattleScenario)\b[\s\S]*?^\};/gm;
|
||||
|
||||
Reference in New Issue
Block a user