From 14597ab17c8442d60ad9add7a295f7cf31ce3286 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 16:29:36 +0900 Subject: [PATCH] Validate sortie flow display fields --- scripts/verify-campaign-flow-data.mjs | 39 +++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/scripts/verify-campaign-flow-data.mjs b/scripts/verify-campaign-flow-data.mjs index e1395a2..64de7b4 100644 --- a/scripts/verify-campaign-flow-data.mjs +++ b/scripts/verify-campaign-flow-data.mjs @@ -12,9 +12,11 @@ const routedBattleSteps = extractObjectStringEntries(campaignRoutingSource, 'bat const battleStepRules = extractCampaignBattleSteps(campaignStateSource); const sortieFlowKeys = extractSortieFlowKeys(campaignFlowSource, battleScenarioIds); const sortieFlowBodies = extractSortieFlowBodies(campaignFlowSource, battleScenarioIds); +const sortieFlowAfterBattleIds = extractSortieFlowAfterBattleIds(campaignFlowSource, battleScenarioIds); const sortieFlowCampaignSteps = extractSortieFlowCampaignSteps(campaignFlowSource); const sortieFlowNextBattleIds = extractSortieFlowNextBattleIds(campaignFlowSource, battleScenarioIds); const campaignRewardUnlockBattleIds = extractCampaignRewardUnlockBattleIds(battlesSource, battleScenarioIds); +const sortieFlowDisplayFields = ['eyebrow', 'title', 'description', 'rewardHint']; const failures = []; @@ -42,6 +44,19 @@ for (const [flowKey, nextBattleId] of sortieFlowNextBattleIds.entries()) { assert(battleIds.has(nextBattleId), `Sortie flow ${flowKey} references unknown next battle id: ${nextBattleId}`); } +for (const flowKey of sortieFlowKeys) { + const flowBody = sortieFlowBodies.get(flowKey) ?? ''; + const afterBattleId = sortieFlowAfterBattleIds.get(flowKey); + assert(afterBattleId, `Sortie flow ${flowKey} has no afterBattleId`); + if (afterBattleId) { + assert(battleIds.has(afterBattleId), `Sortie flow ${flowKey} references unknown afterBattleId: ${afterBattleId}`); + } + assert(/\bpages\s*:/.test(flowBody), `Sortie flow ${flowKey} must provide story pages`); + for (const fieldName of sortieFlowDisplayFields) { + assert(hasVisibleSortieFlowField(flowBody, fieldName), `Sortie flow ${flowKey} must provide a visible ${fieldName}`); + } +} + for (const flowKey of sortieFlowKeys) { const nextBattleId = sortieFlowNextBattleIds.get(flowKey); const campaignStep = sortieFlowCampaignSteps.get(flowKey); @@ -204,6 +219,19 @@ function extractSortieFlowNextBattleIds(source, scenarioIds) { return entries; } +function extractSortieFlowAfterBattleIds(source, scenarioIds) { + const entries = new Map(); + const scenarioEntries = extractSortieFlowProperty(source, /afterBattleId:\s*(\w+BattleScenario)\.id/); + const literalEntries = extractSortieFlowProperty(source, /afterBattleId:\s*'([^']+)'/); + for (const [flowKey, variableName] of scenarioEntries.entries()) { + entries.set(flowKey, scenarioIds.get(variableName) ?? variableName); + } + for (const [flowKey, battleId] of literalEntries.entries()) { + entries.set(flowKey, battleId); + } + return entries; +} + function extractCampaignRewardUnlockBattleIds(source, scenarioIds) { const entries = new Map(); const declarationPattern = /export const (\w+BattleScenario)\b[\s\S]*?^\};/gm; @@ -234,6 +262,17 @@ function extractSortieFlowProperty(source, propertyPattern) { return entries; } +function hasVisibleSortieFlowField(flowBody, fieldName) { + const fieldPattern = new RegExp( + `\\b${fieldName}\\s*:\\s*(?:'([^']*)'|\`([^\`]*)\`|"([^"]*)"|([A-Za-z]\\w*(?:\\.[A-Za-z]\\w*)*))` + ); + const match = fieldPattern.exec(flowBody); + if (!match) { + return false; + } + return (match[1] ?? match[2] ?? match[3] ?? match[4] ?? '').trim().length > 0; +} + function extractConstObject(source, objectName) { const startPattern = new RegExp(`const\\s+${objectName}\\b[^=]*=\\s*\\{`); const startMatch = startPattern.exec(source);