diff --git a/scripts/verify-battle-scenario-data.mjs b/scripts/verify-battle-scenario-data.mjs index d8adf9e..68c26f1 100644 --- a/scripts/verify-battle-scenario-data.mjs +++ b/scripts/verify-battle-scenario-data.mjs @@ -20,6 +20,15 @@ try { const itemKeys = new Set(Object.keys(itemCatalog)); const itemNames = new Set(Object.values(itemCatalog).map((item) => item.name)); const formationRoles = new Set(sortieFormationRoles); + const tacticalGuideEventKeys = new Set([ + 'leader-wavering', + 'ally-danger', + 'village-approach', + 'village-secured', + 'vanguard-broken', + 'cavalry-approach', + 'enemy-posture' + ]); const knownRewardUnitIds = new Set([ ...scenarioEntries.flatMap(([, scenario]) => scenario.units.map((unit) => unit.id)), ...(campaignRecruitUnits ?? []).map((unit) => unit.id) @@ -38,6 +47,7 @@ try { validateMap(errors, scenario, terrainKeys, context); validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context); validateObjectives(errors, scenario, context); + validateTacticalGuide(errors, scenario, tacticalGuideEventKeys, context); validateSortie(errors, scenario, classKeys, formationRoles, terrainRules, context); validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context); }); @@ -55,8 +65,9 @@ try { (total, [, scenario]) => total + scenario.itemRewards.length + campaignRewardLabelCount(scenario.campaignReward), 0 ); + const tacticalGuideCount = scenarioEntries.reduce((total, [, scenario]) => total + (scenario.tacticalGuide ? 1 : 0), 0); console.log( - `Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, objectives, sortie data, equipment, and ${rewardCount} reward labels.` + `Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, objectives, sortie data, ${tacticalGuideCount} tactical guides, equipment, and ${rewardCount} reward labels.` ); } } finally { @@ -190,6 +201,52 @@ function validateObjectives(errors, scenario, context) { }); } +function validateTacticalGuide(errors, scenario, eventKeys, context) { + const guide = scenario.tacticalGuide; + if (!guide) { + return; + } + + assertNonEmptyString(errors, guide.summary, `${context}: tacticalGuide summary`); + assertNonEmptyString(errors, guide.route, `${context}: tacticalGuide route`); + assertNonEmptyString(errors, guide.focus, `${context}: tacticalGuide focus`); + + if (!Array.isArray(guide.roles) || guide.roles.length === 0) { + errors.push(`${context}: tacticalGuide roles must include at least one role`); + } else { + guide.roles.forEach((role, index) => { + assertNonEmptyString(errors, role, `${context}: tacticalGuide roles[${index}]`); + }); + } + + if (guide.events === undefined) { + return; + } + if (!guide.events || typeof guide.events !== 'object' || Array.isArray(guide.events)) { + errors.push(`${context}: tacticalGuide events must be an object`); + return; + } + + Object.entries(guide.events).forEach(([eventKey, event]) => { + if (!eventKeys.has(eventKey)) { + errors.push(`${context}: tacticalGuide event "${eventKey}" is not supported`); + } + if (!event || typeof event !== 'object' || Array.isArray(event)) { + errors.push(`${context}: tacticalGuide event "${eventKey}" must be an object`); + return; + } + + assertNonEmptyString(errors, event.title, `${context}: tacticalGuide event "${eventKey}" title`); + if (!Array.isArray(event.lines) || event.lines.length === 0) { + errors.push(`${context}: tacticalGuide event "${eventKey}" must include at least one line`); + return; + } + event.lines.forEach((line, index) => { + assertNonEmptyString(errors, line, `${context}: tacticalGuide event "${eventKey}" lines[${index}]`); + }); + }); +} + function validateCharacterProgress(errors, unit, context) { if (!Number.isInteger(unit.level) || unit.level < 1) { errors.push(`${context}: invalid level ${unit.level}`);