From 8b6b8cdf54137d2b40116443932fd2211b9322e4 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Sun, 5 Jul 2026 15:48:48 +0900 Subject: [PATCH] Validate camp battle id references --- scripts/verify-camp-reward-data.mjs | 95 +++++++++++++++++++++++------ 1 file changed, 76 insertions(+), 19 deletions(-) diff --git a/scripts/verify-camp-reward-data.mjs b/scripts/verify-camp-reward-data.mjs index aa789ac..0df0835 100644 --- a/scripts/verify-camp-reward-data.mjs +++ b/scripts/verify-camp-reward-data.mjs @@ -1,28 +1,42 @@ import { readFileSync } from 'node:fs'; +import { createServer } from 'vite'; 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)); -const campBattleIdKeys = collectCampBattleIdKeys(source); -const dialogueEvents = collectCampEvents(source, 'campDialogues'); -const visitEvents = collectCampEvents(source, 'campVisits'); -validateCampEventCollection(dialogueEvents, 'campDialogues'); -validateCampEventCollection(visitEvents, 'campVisits'); -validateNumericProperties('gold', { min: 1, max: 20000 }); -validateNumericProperties('bondExp', { min: 0, max: 200 }); -validateNumericProperties('rewardExp', { min: 1, max: 200 }); +const server = await createServer({ + logLevel: 'error', + server: { middlewareMode: true }, + appType: 'custom' +}); -if (errors.length > 0) { - throw new Error(`Camp reward data verification failed:\n${errors.map((error) => `- ${error}`).join('\n')}`); +try { + const { battleScenarios, defaultBattleScenario } = await server.ssrLoadModule('/src/game/data/battles.ts'); + const itemRewardArrays = collectItemRewardArrays(source); + itemRewardArrays.forEach(({ body, index, offset }) => validateItemRewards(body, index, offset)); + const campBattleIdEntries = collectCampBattleIdEntries(source, defaultBattleScenario.id); + const campBattleIdKeys = new Set(campBattleIdEntries.map((entry) => entry.key)); + validateCampBattleIdEntries(campBattleIdEntries, battleScenarios); + const dialogueEvents = collectCampEvents(source, 'campDialogues'); + const visitEvents = collectCampEvents(source, 'campVisits'); + validateCampEventCollection(dialogueEvents, 'campDialogues', campBattleIdKeys); + validateCampEventCollection(visitEvents, 'campVisits', campBattleIdKeys); + 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 ${dialogueEvents.length} camp dialogues, ${visitEvents.length} camp visits, ${campBattleIdEntries.length} camp battle ids, ${itemRewardArrays.length} camp item reward lists, and camp reward numeric fields.` + ); +} finally { + await server.close(); } -console.log( - `Verified ${dialogueEvents.length} camp dialogues, ${visitEvents.length} camp visits, ${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], @@ -63,9 +77,52 @@ function validateNumericProperties(propertyName, { min, max }) { } } -function collectCampBattleIdKeys(text) { +function collectCampBattleIdEntries(text, defaultBattleScenarioId) { const objectSource = extractConstObject(text, 'campBattleIds'); - return new Set([...objectSource.matchAll(/^\s*(\w+):/gm)].map((match) => match[1])); + const objectOffset = text.indexOf(objectSource); + return [...objectSource.matchAll(/^\s*(\w+):\s*([^,\n]+)/gm)].map((match) => { + const expression = match[2].trim(); + return { + key: match[1], + expression, + value: parseCampBattleIdExpression(expression, defaultBattleScenarioId), + offset: objectOffset + (match.index ?? 0) + }; + }); +} + +function parseCampBattleIdExpression(expression, defaultBattleScenarioId) { + const stringMatch = /^'([^']+)'$/.exec(expression); + if (stringMatch) { + return stringMatch[1]; + } + if (expression === 'defaultBattleScenario.id') { + return defaultBattleScenarioId; + } + return ''; +} + +function validateCampBattleIdEntries(entries, battleScenarios) { + const knownBattleIds = new Set(Object.keys(battleScenarios)); + const seenBattleIds = new Map(); + entries.forEach((entry) => { + const context = `campBattleIds.${entry.key}`; + if (!entry.value) { + errors.push(`${sourcePath}:${lineNumber(entry.offset)} ${context} uses unsupported expression "${entry.expression}"`); + return; + } + if (!knownBattleIds.has(entry.value)) { + errors.push(`${sourcePath}:${lineNumber(entry.offset)} ${context} references unknown battle id "${entry.value}"`); + } + if (seenBattleIds.has(entry.value)) { + errors.push( + `${sourcePath}:${lineNumber(entry.offset)} ${context} duplicates battle id "${entry.value}" first used by campBattleIds.${seenBattleIds.get( + entry.value + )}` + ); + } + seenBattleIds.set(entry.value, entry.key); + }); } function collectCampEvents(text, arrayName) { @@ -76,7 +133,7 @@ function collectCampEvents(text, arrayName) { })); } -function validateCampEventCollection(events, collectionName) { +function validateCampEventCollection(events, collectionName, campBattleIdKeys) { const seenIds = new Map(); if (events.length === 0) { errors.push(`${sourcePath}: ${collectionName} has no entries`);