import { getCharacterHomeSceneId, getCharacterNpcSceneIds, PRESET_CHARACTERS } from '../src/data/characterPresets.ts'; import { MONSTER_PRESETS_BY_WORLD } from '../src/data/hostileNpcPresets.ts'; import { getSceneHostileNpcPresetIds, getScenePresetsByWorld } from '../src/data/scenePresets.ts'; import { buildStateFunctionDefinitions } from '../src/data/stateFunctions.ts'; import { WorldType } from '../src/types.ts'; function addError(errors: string[], message: string) { errors.push(message); } function validateScenes(errors: string[]) { for (const worldType of [WorldType.WUXIA, WorldType.XIANXIA]) { const scenes = getScenePresetsByWorld(worldType); const sceneIdSet = new Set(scenes.map(scene => scene.id)); const monsterIdSet = new Set(MONSTER_PRESETS_BY_WORLD[worldType].map(monster => monster.id)); const duplicateSceneIds = scenes .map(scene => scene.id) .filter((id, index, all) => all.indexOf(id) !== index); duplicateSceneIds.forEach(sceneId => { addError(errors, `[scene] duplicate id "${sceneId}" in ${worldType}`); }); scenes.forEach(scene => { if (scene.forwardSceneId && !sceneIdSet.has(scene.forwardSceneId)) { addError(errors, `[scene] ${scene.id} forwardSceneId "${scene.forwardSceneId}" not found in ${worldType}`); } scene.connectedSceneIds.forEach(connectedSceneId => { if (!sceneIdSet.has(connectedSceneId)) { addError(errors, `[scene] ${scene.id} connectedSceneId "${connectedSceneId}" not found in ${worldType}`); } }); getSceneHostileNpcPresetIds(scene).forEach(monsterId => { if (!monsterIdSet.has(monsterId)) { addError(errors, `[scene] ${scene.id} references unknown monster "${monsterId}" in ${worldType}`); } }); const npcIds = new Set(); scene.npcs.forEach(npc => { if (npcIds.has(npc.id)) { addError(errors, `[scene] ${scene.id} has duplicate npc id "${npc.id}"`); } npcIds.add(npc.id); if (npc.characterId && !PRESET_CHARACTERS.some(character => character.id === npc.characterId)) { addError(errors, `[scene] ${scene.id} npc "${npc.id}" references unknown character "${npc.characterId}"`); } }); }); } } function validateCharacters(errors: string[]) { for (const worldType of [WorldType.WUXIA, WorldType.XIANXIA]) { const sceneIdSet = new Set(getScenePresetsByWorld(worldType).map(scene => scene.id)); PRESET_CHARACTERS.forEach(character => { const homeSceneId = getCharacterHomeSceneId(worldType, character.id); if (homeSceneId && !sceneIdSet.has(homeSceneId)) { addError(errors, `[character] ${character.id} homeSceneId "${homeSceneId}" not found in ${worldType}`); } getCharacterNpcSceneIds(worldType, character.id).forEach(sceneId => { if (!sceneIdSet.has(sceneId)) { addError(errors, `[character] ${character.id} npc scene "${sceneId}" not found in ${worldType}`); } }); }); } } function validateStateFunctions(errors: string[]) { const definitions = buildStateFunctionDefinitions(); const duplicateIds = definitions .map(definition => definition.id) .filter((id, index, all) => all.indexOf(id) !== index); duplicateIds.forEach(id => { addError(errors, `[function] duplicate function id "${id}"`); }); definitions.forEach(definition => { if (!definition.text.trim()) { addError(errors, `[function] ${definition.id} has empty text`); } if (!definition.description.trim()) { addError(errors, `[function] ${definition.id} has empty description`); } }); } function main() { const errors: string[] = []; validateScenes(errors); validateCharacters(errors); validateStateFunctions(errors); if (errors.length > 0) { console.error(`Content validation failed with ${errors.length} issue(s):`); errors.forEach(error => console.error(`- ${error}`)); process.exitCode = 1; return; } const sceneCount = getScenePresetsByWorld(WorldType.WUXIA).length + getScenePresetsByWorld(WorldType.XIANXIA).length; const monsterCount = MONSTER_PRESETS_BY_WORLD[WorldType.WUXIA].length + MONSTER_PRESETS_BY_WORLD[WorldType.XIANXIA].length; const functionCount = buildStateFunctionDefinitions().length; console.log(`Content validation passed. scenes=${sceneCount} monsters=${monsterCount} characters=${PRESET_CHARACTERS.length} functions=${functionCount}`); } main();