Split custom world generation into staged lightweight batches
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-05 22:20:30 +08:00
parent 89cecda7da
commit fcd8d727b0
57 changed files with 7646 additions and 1425 deletions

View File

@@ -4,6 +4,7 @@ import {
buildItemAttributeResonance,
} from '../data/attributeProfileGenerator';
import { mergeCustomWorldPlayableNpcTags } from '../data/customWorldBuildTags';
import { normalizeCustomWorldLandmarks } from '../data/customWorldSceneGraph';
import { CustomWorldProfile, WorldType } from '../types';
import { normalizeCustomWorldProfile } from './customWorld';
@@ -95,37 +96,91 @@ export function buildExpandedCustomWorldProfile(
): CustomWorldProfile {
const profile = normalizeCustomWorldProfile(raw, settingText);
const attributeSchema = profile.attributeSchema;
const playableNpcs = dedupeByName(profile.playableNpcs)
.slice(0, PLAYABLE_TEMPLATE_CHARACTER_IDS.length)
.map((npc, index) => {
const templateCharacterId =
npc.templateCharacterId ?? getPlayableTemplateCharacterId(index);
return {
...npc,
id: createEntryId('playable-npc', npc.name, index),
templateCharacterId,
tags: mergeCustomWorldPlayableNpcTags(profile, npc, {
templateCharacterId,
maxCount: 5,
}),
attributeProfile:
npc.attributeProfile ??
buildCustomWorldPlayableNpcAttributeProfile(npc, attributeSchema),
};
});
const storyNpcs = dedupeByName(profile.storyNpcs).map((npc, index) => ({
...npc,
id: createEntryId('story-npc', npc.name, index),
description: clampText(npc.description, 72),
motivation: clampText(npc.motivation, 72),
relationshipHooks: normalizeHooks(npc.relationshipHooks),
attributeProfile:
npc.attributeProfile ??
buildCustomWorldStoryNpcAttributeProfile(npc, attributeSchema),
}));
const storyNpcIdByReference = new Map<string, string>();
storyNpcs.forEach((npc) => {
storyNpcIdByReference.set(npc.id, npc.id);
storyNpcIdByReference.set(npc.name, npc.id);
});
profile.storyNpcs.forEach((npc) => {
const nextNpc = storyNpcs.find((entry) => entry.name === npc.name);
if (!nextNpc) {
return;
}
storyNpcIdByReference.set(npc.id, nextNpc.id);
storyNpcIdByReference.set(npc.name, nextNpc.id);
});
const landmarkDrafts = dedupeByName(profile.landmarks).map((landmark, index) => ({
...landmark,
id: createEntryId('landmark', landmark.name, index),
description: clampText(landmark.description, 96),
dangerLevel:
landmark.dangerLevel ||
(profile.templateWorldType === WorldType.XIANXIA ? 'high' : 'medium'),
}));
const landmarkIdByReference = new Map<string, string>();
landmarkDrafts.forEach((landmark) => {
landmarkIdByReference.set(landmark.id, landmark.id);
landmarkIdByReference.set(landmark.name, landmark.id);
});
profile.landmarks.forEach((landmark) => {
const nextLandmark = landmarkDrafts.find(
(entry) => entry.name === landmark.name,
);
if (!nextLandmark) {
return;
}
landmarkIdByReference.set(landmark.id, nextLandmark.id);
landmarkIdByReference.set(landmark.name, nextLandmark.id);
});
const landmarks = normalizeCustomWorldLandmarks({
landmarks: landmarkDrafts.map((landmark) => ({
...landmark,
sceneNpcIds: landmark.sceneNpcIds.map(
(npcId) => storyNpcIdByReference.get(npcId) ?? npcId,
),
connections: landmark.connections.map((connection) => ({
targetLandmarkId:
landmarkIdByReference.get(connection.targetLandmarkId) ??
connection.targetLandmarkId,
relativePosition: connection.relativePosition,
summary: connection.summary,
})),
})),
storyNpcs,
});
return {
...profile,
playableNpcs: dedupeByName(profile.playableNpcs)
.slice(0, PLAYABLE_TEMPLATE_CHARACTER_IDS.length)
.map((npc, index) => {
const templateCharacterId =
npc.templateCharacterId ?? getPlayableTemplateCharacterId(index);
return {
...npc,
id: createEntryId('playable-npc', npc.name, index),
templateCharacterId,
tags: mergeCustomWorldPlayableNpcTags(profile, npc, {
templateCharacterId,
maxCount: 5,
}),
attributeProfile:
npc.attributeProfile ??
buildCustomWorldPlayableNpcAttributeProfile(npc, attributeSchema),
};
}),
storyNpcs: dedupeByName(profile.storyNpcs).map((npc, index) => ({
...npc,
id: createEntryId('story-npc', npc.name, index),
description: clampText(npc.description, 72),
motivation: clampText(npc.motivation, 72),
relationshipHooks: normalizeHooks(npc.relationshipHooks),
attributeProfile:
npc.attributeProfile ??
buildCustomWorldStoryNpcAttributeProfile(npc, attributeSchema),
})),
playableNpcs,
storyNpcs,
items: dedupeByName(profile.items).map((item, index) => ({
...item,
id: createEntryId('item', item.name, index),
@@ -134,13 +189,6 @@ export function buildExpandedCustomWorldProfile(
attributeResonance:
item.attributeResonance ?? buildItemAttributeResonance(item),
})),
landmarks: dedupeByName(profile.landmarks).map((landmark, index) => ({
...landmark,
id: createEntryId('landmark', landmark.name, index),
description: clampText(landmark.description, 96),
dangerLevel:
landmark.dangerLevel ||
(profile.templateWorldType === WorldType.XIANXIA ? 'high' : 'medium'),
})),
landmarks,
};
}