Validate camp reward data

This commit is contained in:
2026-07-05 08:23:44 +09:00
parent 85500e419a
commit c4c0e3abed
3 changed files with 76 additions and 0 deletions

View File

@@ -13,6 +13,7 @@
"measure:performance": "node scripts/measure-performance.mjs",
"verify:battle-data": "node scripts/verify-battle-scenario-data.mjs",
"verify:campaign-data": "node scripts/verify-campaign-flow-data.mjs",
"verify:camp-rewards": "node scripts/verify-camp-reward-data.mjs",
"verify:story-assets": "node scripts/verify-story-asset-data.mjs",
"verify:story-images": "node scripts/verify-story-image-asset-data.mjs",
"verify:visual-assets": "node scripts/verify-visual-asset-data.mjs",

View File

@@ -0,0 +1,74 @@
import { readFileSync } from 'node:fs';
const sourcePath = 'src/game/scenes/CampScene.ts';
const source = readFileSync(sourcePath, 'utf8');
const errors = [];
const itemRewardArrays = collectItemRewardArrays(source);
itemRewardArrays.forEach(({ body, index, offset }) => validateItemRewards(body, index, offset));
validateNumericProperties('gold', { min: 1, max: 20000 });
validateNumericProperties('bondExp', { min: 0, max: 200 });
validateNumericProperties('rewardExp', { min: 1, max: 200 });
if (errors.length > 0) {
throw new Error(`Camp reward data verification failed:\n${errors.map((error) => `- ${error}`).join('\n')}`);
}
console.log(`Verified ${itemRewardArrays.length} camp item reward lists and camp reward numeric fields.`);
function collectItemRewardArrays(text) {
return [...text.matchAll(/itemRewards:\s*\[([\s\S]*?)\]/g)].map((match, index) => ({
body: match[1],
index,
offset: match.index ?? 0
}));
}
function validateItemRewards(body, index, offset) {
const entries = [...body.matchAll(/'((?:\\'|[^'])*)'/g)].map((match) => match[1].replace(/\\'/g, "'"));
if (entries.length === 0) {
return;
}
const labels = new Set();
entries.forEach((reward, rewardIndex) => {
const parsed = parseRewardLabel(reward);
if (!parsed.label) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}][${rewardIndex}] has an empty parsed label`);
}
if (!Number.isInteger(parsed.amount) || parsed.amount < 1) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}][${rewardIndex}] has invalid amount ${parsed.amount}`);
}
if (labels.has(parsed.label)) {
errors.push(`${sourcePath}:${lineNumber(offset)} itemRewards[${index}] has duplicate label "${parsed.label}"`);
}
labels.add(parsed.label);
});
}
function validateNumericProperties(propertyName, { min, max }) {
const pattern = new RegExp(`${propertyName}:\\s*(-?\\d+)`, 'g');
for (const match of source.matchAll(pattern)) {
const value = Number(match[1]);
if (!Number.isInteger(value) || value < min || value > max) {
errors.push(`${sourcePath}:${lineNumber(match.index ?? 0)} ${propertyName} has out-of-range value ${value}`);
}
}
}
function parseRewardLabel(reward) {
const normalized = String(reward).replace(/\s+/g, ' ').trim();
const match = normalized.match(/^(.*?)(?:\s*[+xX]?\s*(\d+))$/);
if (!match) {
return { label: normalized, amount: 1 };
}
return {
label: match[1].trim() || normalized,
amount: Number(match[2] ?? 1)
};
}
function lineNumber(offset) {
return source.slice(0, offset).split(/\r?\n/).length;
}

View File

@@ -11,6 +11,7 @@ let browser;
try {
runCommand('node', ['scripts/verify-campaign-flow-data.mjs']);
runCommand('node', ['scripts/verify-battle-scenario-data.mjs']);
runCommand('node', ['scripts/verify-camp-reward-data.mjs']);
runCommand('node', ['scripts/verify-story-asset-data.mjs']);
runCommand('node', ['scripts/verify-story-image-asset-data.mjs']);
runCommand('node', ['scripts/verify-visual-asset-data.mjs']);