init with react+axum+spacetimedb
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-26 18:06:23 +08:00
commit cbc27bad4a
20199 changed files with 883714 additions and 0 deletions

116
scripts/validate-content.ts Normal file
View File

@@ -0,0 +1,116 @@
import { getCharacterHomeSceneId, getCharacterNpcSceneIds, ROLE_TEMPLATE_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<string>();
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 && !ROLE_TEMPLATE_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));
ROLE_TEMPLATE_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=${ROLE_TEMPLATE_CHARACTERS.length} functions=${functionCount}`);
}
main();