From 7203403897fbd8a7d17523a5a25c128a1f5eca05 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 14:58:14 +0900 Subject: [PATCH] Validate sortie recruit availability --- scripts/verify-campaign-recruit-data.mjs | 39 +++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/scripts/verify-campaign-recruit-data.mjs b/scripts/verify-campaign-recruit-data.mjs index 52d5224..1166afe 100644 --- a/scripts/verify-campaign-recruit-data.mjs +++ b/scripts/verify-campaign-recruit-data.mjs @@ -21,7 +21,9 @@ try { validateRecruitTemplates(errors, recruitTemplates, classKeys, itemKeys, itemCatalog, equipmentSlots); - Object.entries(battleScenarios).forEach(([battleId, scenario]) => { + const scenarioEntries = Object.entries(battleScenarios); + + scenarioEntries.forEach(([battleId, scenario]) => { const sameBattleAllies = new Set(scenario.units.filter((unit) => unit.faction === 'ally').map((unit) => unit.id)); const rewardRecruits = scenario.campaignReward?.recruits ?? []; @@ -40,6 +42,7 @@ try { }); validateReachability(errors, recruitTemplates, recruitRewardRefs); + validateSortieRecruitAvailability(errors, scenarioEntries); if (errors.length) { console.error(`Campaign recruit data verification failed with ${errors.length} issue(s):`); @@ -165,6 +168,40 @@ function validateReachability(errors, recruitTemplates, recruitRewardRefs) { }); } +function validateSortieRecruitAvailability(errors, scenarioEntries) { + const firstBattleAllies = scenarioEntries[0]?.[1]?.units + ?.filter((unit) => unit.faction === 'ally') + .map((unit) => unit.id) ?? []; + const availableUnitIds = new Set(firstBattleAllies); + + scenarioEntries.forEach(([battleId, scenario]) => { + const unavailableRefs = new Set(); + const sortie = scenario.sortie; + + (sortie?.requiredUnits ?? []).forEach((unitId) => { + if (!availableUnitIds.has(unitId)) { + unavailableRefs.add(`requiredUnits:${unitId}`); + } + }); + (sortie?.recommendedUnits ?? []).forEach((recommendation) => { + if (!availableUnitIds.has(recommendation.unitId)) { + unavailableRefs.add(`recommendedUnits:${recommendation.unitId}`); + } + }); + (sortie?.deploymentSlots ?? []).forEach((slot) => { + if (slot.unitId && !availableUnitIds.has(slot.unitId)) { + unavailableRefs.add(`deploymentSlots:${slot.unitId}`); + } + }); + + if (unavailableRefs.size > 0) { + errors.push(`${battleId}: sortie references unit(s) before recruit availability: ${Array.from(unavailableRefs).join(', ')}`); + } + + (scenario.campaignReward?.recruits ?? []).forEach((unitId) => availableUnitIds.add(unitId)); + }); +} + function assertNonEmptyString(errors, value, context) { if (typeof value !== 'string' || value.trim().length === 0) { errors.push(`${context} must be a non-empty string`);