feat: deepen audiovisual story immersion

This commit is contained in:
2026-07-23 10:57:07 +09:00
parent a9e401aedf
commit 8cf0886d7d
38 changed files with 2037 additions and 144 deletions

View File

@@ -35,6 +35,9 @@ try {
...(campaignRecruitUnits ?? []).map((unit) => unit.id)
]);
const errors = [];
const expectedRequiredVictoryObjectives = new Map([
['seventeenth-battle-wolong-visit-road', new Set(['scholar-rendezvous', 'longzhong-cottage'])]
]);
scenarioEntries.forEach(([scenarioId, scenario]) => {
const context = scenarioId;
@@ -49,6 +52,7 @@ try {
validateMap(errors, scenario, terrainKeys, context);
validateUnits(errors, scenario, classKeys, itemKeys, equipmentSlots, context);
validateObjectives(errors, scenario, terrainKeys, context);
validateRequiredVictoryObjectives(errors, scenario, expectedRequiredVictoryObjectives.get(scenarioId), context);
validateTacticalGuide(errors, scenario, tacticalGuideEventKeys, context);
validateSortie(errors, scenario, classKeys, formationRoles, terrainRules, context);
validateRewards(errors, scenario, scenarioIds, itemNames, knownRewardUnitIds, context);
@@ -79,8 +83,12 @@ try {
total + scenario.objectives.filter((objective) => objective.kind === 'secure-terrain').length,
0
);
const requiredVictoryObjectiveCount = scenarioEntries.reduce(
(total, [, scenario]) => total + scenario.objectives.filter((objective) => objective.requiredForVictory).length,
0
);
console.log(
`Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, opening briefs, objectives (${secureTerrainObjectiveCount} secure-terrain centers, ${secureTerrainOverrideCount} explicit overrides), sortie data, ${tacticalGuideCount} tactical guides, equipment, and ${rewardCount} reward labels.`
`Verified ${scenarioEntries.length} battle scenarios, ${unitCount} unit placements, maps, opening briefs, objectives (${secureTerrainObjectiveCount} secure-terrain centers, ${requiredVictoryObjectiveCount} required victory gates, ${secureTerrainOverrideCount} explicit overrides), sortie data, ${tacticalGuideCount} tactical guides, equipment, and ${rewardCount} reward labels.`
);
}
} finally {
@@ -206,6 +214,12 @@ function validateObjectives(errors, scenario, terrainKeys, context) {
if (!Number.isFinite(objective.rewardGold) || objective.rewardGold < 0) {
errors.push(`${context}/${objective.id}: invalid rewardGold ${objective.rewardGold}`);
}
if (objective.requiredForVictory !== undefined && typeof objective.requiredForVictory !== 'boolean') {
errors.push(`${context}/${objective.id}: requiredForVictory must be a boolean`);
}
if (objective.requiredForVictory && objective.kind !== 'secure-terrain') {
errors.push(`${context}/${objective.id}: only secure-terrain objectives may gate victory`);
}
if (objective.unitId && !unitIds.has(objective.unitId)) {
errors.push(`${context}/${objective.id}: references missing unit "${objective.unitId}"`);
}
@@ -229,6 +243,30 @@ function validateObjectives(errors, scenario, terrainKeys, context) {
});
}
function validateRequiredVictoryObjectives(errors, scenario, expectedObjectiveIds, context) {
const requiredObjectiveIds = scenario.objectives
.filter((objective) => objective.requiredForVictory)
.map((objective) => objective.id);
if (!expectedObjectiveIds) {
if (requiredObjectiveIds.length > 0) {
errors.push(`${context}: unexpected required victory objectives ${requiredObjectiveIds.join(', ')}`);
}
return;
}
const actualIds = new Set(requiredObjectiveIds);
expectedObjectiveIds.forEach((objectiveId) => {
if (!actualIds.has(objectiveId)) {
errors.push(`${context}: required victory objective "${objectiveId}" is missing`);
}
});
actualIds.forEach((objectiveId) => {
if (!expectedObjectiveIds.has(objectiveId)) {
errors.push(`${context}: unexpected required victory objective "${objectiveId}"`);
}
});
}
function validateSecureTerrainOverrides(errors, battleScenarios, overridesByBattle, terrainKeys) {
if (!overridesByBattle || typeof overridesByBattle !== 'object' || Array.isArray(overridesByBattle)) {
errors.push('secureTerrainCenterOverrides must be an object keyed by battle id');