Tighten sortie scenario data gates
This commit is contained in:
@@ -11,6 +11,7 @@ try {
|
|||||||
const { campaignRecruitUnits } = await server.ssrLoadModule('/src/game/data/scenario.ts');
|
const { campaignRecruitUnits } = await server.ssrLoadModule('/src/game/data/scenario.ts');
|
||||||
const { unitClasses, terrainRules } = await server.ssrLoadModule('/src/game/data/battleRules.ts');
|
const { unitClasses, terrainRules } = await server.ssrLoadModule('/src/game/data/battleRules.ts');
|
||||||
const { itemCatalog, equipmentSlots } = await server.ssrLoadModule('/src/game/data/battleItems.ts');
|
const { itemCatalog, equipmentSlots } = await server.ssrLoadModule('/src/game/data/battleItems.ts');
|
||||||
|
const { sortieFormationRoles } = await server.ssrLoadModule('/src/game/data/sortieDeployment.ts');
|
||||||
|
|
||||||
const scenarioEntries = Object.entries(battleScenarios);
|
const scenarioEntries = Object.entries(battleScenarios);
|
||||||
const scenarioIds = new Set(scenarioEntries.map(([id]) => id));
|
const scenarioIds = new Set(scenarioEntries.map(([id]) => id));
|
||||||
@@ -18,6 +19,7 @@ try {
|
|||||||
const terrainKeys = new Set(Object.keys(terrainRules));
|
const terrainKeys = new Set(Object.keys(terrainRules));
|
||||||
const itemKeys = new Set(Object.keys(itemCatalog));
|
const itemKeys = new Set(Object.keys(itemCatalog));
|
||||||
const itemNames = new Set(Object.values(itemCatalog).map((item) => item.name));
|
const itemNames = new Set(Object.values(itemCatalog).map((item) => item.name));
|
||||||
|
const formationRoles = new Set(sortieFormationRoles);
|
||||||
const knownRewardUnitIds = new Set([
|
const knownRewardUnitIds = new Set([
|
||||||
...scenarioEntries.flatMap(([, scenario]) => scenario.units.map((unit) => unit.id)),
|
...scenarioEntries.flatMap(([, scenario]) => scenario.units.map((unit) => unit.id)),
|
||||||
...(campaignRecruitUnits ?? []).map((unit) => unit.id)
|
...(campaignRecruitUnits ?? []).map((unit) => unit.id)
|
||||||
@@ -36,7 +38,7 @@ try {
|
|||||||
validateMap(errors, scenario, terrainKeys, context);
|
validateMap(errors, scenario, terrainKeys, context);
|
||||||
validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context);
|
validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context);
|
||||||
validateObjectives(errors, scenario, context);
|
validateObjectives(errors, scenario, context);
|
||||||
validateSortie(errors, scenario, classKeys, context);
|
validateSortie(errors, scenario, classKeys, formationRoles, context);
|
||||||
validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context);
|
validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -190,7 +192,7 @@ function validateObjectives(errors, scenario, context) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateSortie(errors, scenario, classKeys, context) {
|
function validateSortie(errors, scenario, classKeys, formationRoles, context) {
|
||||||
const sortie = scenario.sortie;
|
const sortie = scenario.sortie;
|
||||||
if (!sortie) {
|
if (!sortie) {
|
||||||
return;
|
return;
|
||||||
@@ -204,20 +206,55 @@ function validateSortie(errors, scenario, classKeys, context) {
|
|||||||
errors.push(`${context}: requiredUnits count exceeds sortieLimit`);
|
errors.push(`${context}: requiredUnits count exceeds sortieLimit`);
|
||||||
}
|
}
|
||||||
|
|
||||||
[...(sortie.requiredUnits ?? []), ...(sortie.excludedUnits ?? [])].forEach((unitId) => {
|
const requiredUnits = sortie.requiredUnits ?? [];
|
||||||
|
const excludedUnits = sortie.excludedUnits ?? [];
|
||||||
|
const requiredUnitIds = new Set();
|
||||||
|
const excludedUnitIds = new Set();
|
||||||
|
|
||||||
|
requiredUnits.forEach((unitId) => {
|
||||||
|
if (requiredUnitIds.has(unitId)) {
|
||||||
|
errors.push(`${context}: duplicate required unit "${unitId}"`);
|
||||||
|
}
|
||||||
|
requiredUnitIds.add(unitId);
|
||||||
|
});
|
||||||
|
excludedUnits.forEach((unitId) => {
|
||||||
|
if (excludedUnitIds.has(unitId)) {
|
||||||
|
errors.push(`${context}: duplicate excluded unit "${unitId}"`);
|
||||||
|
}
|
||||||
|
excludedUnitIds.add(unitId);
|
||||||
|
if (requiredUnitIds.has(unitId)) {
|
||||||
|
errors.push(`${context}: unit "${unitId}" cannot be both required and excluded`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
[...requiredUnits, ...excludedUnits].forEach((unitId) => {
|
||||||
if (!unitIds.has(unitId)) {
|
if (!unitIds.has(unitId)) {
|
||||||
errors.push(`${context}: sortie references missing unit "${unitId}"`);
|
errors.push(`${context}: sortie references missing unit "${unitId}"`);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const recommendedUnitIds = new Set();
|
||||||
sortie.recommendedUnits.forEach((recommendation) => {
|
sortie.recommendedUnits.forEach((recommendation) => {
|
||||||
|
if (recommendedUnitIds.has(recommendation.unitId)) {
|
||||||
|
errors.push(`${context}: duplicate recommended unit "${recommendation.unitId}"`);
|
||||||
|
}
|
||||||
|
recommendedUnitIds.add(recommendation.unitId);
|
||||||
if (!unitIds.has(recommendation.unitId)) {
|
if (!unitIds.has(recommendation.unitId)) {
|
||||||
errors.push(`${context}: recommended unit "${recommendation.unitId}" is missing from units`);
|
errors.push(`${context}: recommended unit "${recommendation.unitId}" is missing from units`);
|
||||||
}
|
}
|
||||||
|
if (excludedUnitIds.has(recommendation.unitId)) {
|
||||||
|
errors.push(`${context}: excluded unit "${recommendation.unitId}" cannot also be recommended`);
|
||||||
|
}
|
||||||
|
if (recommendation.role && !formationRoles.has(recommendation.role)) {
|
||||||
|
errors.push(`${context}/${recommendation.unitId}: invalid recommendation role "${recommendation.role}"`);
|
||||||
|
}
|
||||||
assertNonEmptyString(errors, recommendation.reason, `${context}/${recommendation.unitId}: recommendation reason`);
|
assertNonEmptyString(errors, recommendation.reason, `${context}/${recommendation.unitId}: recommendation reason`);
|
||||||
});
|
});
|
||||||
sortie.recommendedClasses.forEach((recommendation) => {
|
sortie.recommendedClasses.forEach((recommendation) => {
|
||||||
assertNonEmptyString(errors, recommendation.label, `${context}: recommended class label`);
|
assertNonEmptyString(errors, recommendation.label, `${context}: recommended class label`);
|
||||||
assertNonEmptyString(errors, recommendation.reason, `${context}/${recommendation.label}: recommended class reason`);
|
assertNonEmptyString(errors, recommendation.reason, `${context}/${recommendation.label}: recommended class reason`);
|
||||||
|
if (!Array.isArray(recommendation.classKeys) || recommendation.classKeys.length === 0) {
|
||||||
|
errors.push(`${context}/${recommendation.label}: recommended class must include at least one classKey`);
|
||||||
|
}
|
||||||
recommendation.classKeys.forEach((classKey) => {
|
recommendation.classKeys.forEach((classKey) => {
|
||||||
if (!classKeys.has(classKey)) {
|
if (!classKeys.has(classKey)) {
|
||||||
errors.push(`${context}: recommended class "${recommendation.label}" references unknown classKey "${classKey}"`);
|
errors.push(`${context}: recommended class "${recommendation.label}" references unknown classKey "${classKey}"`);
|
||||||
@@ -229,11 +266,15 @@ function validateSortie(errors, scenario, classKeys, context) {
|
|||||||
errors.push(`${context}: deploymentSlots count is lower than sortieLimit`);
|
errors.push(`${context}: deploymentSlots count is lower than sortieLimit`);
|
||||||
}
|
}
|
||||||
const deploymentTiles = new Set();
|
const deploymentTiles = new Set();
|
||||||
|
const deploymentUnitIds = new Set();
|
||||||
sortie.deploymentSlots.forEach((slot) => {
|
sortie.deploymentSlots.forEach((slot) => {
|
||||||
if (!isInBounds(slot, scenario.map)) {
|
if (!isInBounds(slot, scenario.map)) {
|
||||||
errors.push(`${context}: deployment slot ${slot.x},${slot.y} is outside map`);
|
errors.push(`${context}: deployment slot ${slot.x},${slot.y} is outside map`);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (slot.role && !formationRoles.has(slot.role)) {
|
||||||
|
errors.push(`${context}: deployment slot ${slot.x},${slot.y} has invalid role "${slot.role}"`);
|
||||||
|
}
|
||||||
const tileKey = `${slot.x},${slot.y}`;
|
const tileKey = `${slot.x},${slot.y}`;
|
||||||
if (deploymentTiles.has(tileKey)) {
|
if (deploymentTiles.has(tileKey)) {
|
||||||
errors.push(`${context}: duplicate deployment slot ${tileKey}`);
|
errors.push(`${context}: duplicate deployment slot ${tileKey}`);
|
||||||
@@ -242,6 +283,12 @@ function validateSortie(errors, scenario, classKeys, context) {
|
|||||||
if (slot.unitId && !unitIds.has(slot.unitId)) {
|
if (slot.unitId && !unitIds.has(slot.unitId)) {
|
||||||
errors.push(`${context}: deployment slot references missing unit "${slot.unitId}"`);
|
errors.push(`${context}: deployment slot references missing unit "${slot.unitId}"`);
|
||||||
}
|
}
|
||||||
|
if (slot.unitId) {
|
||||||
|
if (deploymentUnitIds.has(slot.unitId)) {
|
||||||
|
errors.push(`${context}: duplicate deployment unit slot "${slot.unitId}"`);
|
||||||
|
}
|
||||||
|
deploymentUnitIds.add(slot.unitId);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user