Rework story engine flow and reorganize project docs
Some checks failed
CI / verify (push) Has been cancelled

This commit is contained in:
2026-04-06 23:19:00 +08:00
parent d678929064
commit ddcb5d5c8c
241 changed files with 19805 additions and 2478 deletions

View File

@@ -7,6 +7,23 @@ import { mergeCustomWorldPlayableNpcTags } from '../data/customWorldBuildTags';
import { normalizeCustomWorldLandmarks } from '../data/customWorldSceneGraph';
import { CustomWorldProfile, WorldType } from '../types';
import { normalizeCustomWorldProfile } from './customWorld';
import {
buildFallbackActorNarrativeProfile,
normalizeActorNarrativeProfile,
} from './storyEngine/actorNarrativeProfile';
import { compileCampaignFromWorldProfile } from './storyEngine/campaignPackCompiler';
import { buildKnowledgeGraph } from './storyEngine/knowledgeGraph';
import { registerScenarioPack } from './storyEngine/scenarioPackRegistry';
import { buildSceneNarrativeResidues } from './storyEngine/sceneResidueCompiler';
import {
buildThemePackFromWorldProfile,
normalizeThemePack,
} from './storyEngine/themePack';
import { buildThreadContractsFromProfile } from './storyEngine/threadContract';
import {
buildFallbackWorldStoryGraph,
normalizeWorldStoryGraph,
} from './storyEngine/worldStoryGraph';
const PLAYABLE_TEMPLATE_CHARACTER_IDS = [
'sword-princess',
@@ -103,7 +120,7 @@ export function buildExpandedCustomWorldProfile(
npc.templateCharacterId ?? getPlayableTemplateCharacterId(index);
return {
...npc,
id: createEntryId('playable-npc', npc.name, index),
id: npc.id || createEntryId('playable-npc', npc.name, index),
templateCharacterId,
tags: mergeCustomWorldPlayableNpcTags(profile, npc, {
templateCharacterId,
@@ -116,7 +133,7 @@ export function buildExpandedCustomWorldProfile(
});
const storyNpcs = dedupeByName(profile.storyNpcs).map((npc, index) => ({
...npc,
id: createEntryId('story-npc', npc.name, index),
id: npc.id || createEntryId('story-npc', npc.name, index),
description: clampText(npc.description, 72),
motivation: clampText(npc.motivation, 72),
relationshipHooks: normalizeHooks(npc.relationshipHooks),
@@ -139,7 +156,7 @@ export function buildExpandedCustomWorldProfile(
});
const landmarkDrafts = dedupeByName(profile.landmarks).map((landmark, index) => ({
...landmark,
id: createEntryId('landmark', landmark.name, index),
id: landmark.id || createEntryId('landmark', landmark.name, index),
description: clampText(landmark.description, 96),
dangerLevel:
landmark.dangerLevel ||
@@ -176,19 +193,90 @@ export function buildExpandedCustomWorldProfile(
})),
storyNpcs,
});
return {
const items = dedupeByName(profile.items).map((item, index) => ({
...item,
id: item.id || createEntryId('item', item.name, index),
description: clampText(item.description, 72),
tags: normalizeTags(item.tags),
attributeResonance:
item.attributeResonance ?? buildItemAttributeResonance(item),
}));
const baseExpandedProfile = {
...profile,
playableNpcs,
storyNpcs,
items: dedupeByName(profile.items).map((item, index) => ({
...item,
id: createEntryId('item', item.name, index),
description: clampText(item.description, 72),
tags: normalizeTags(item.tags),
attributeResonance:
item.attributeResonance ?? buildItemAttributeResonance(item),
})),
items,
landmarks,
} satisfies CustomWorldProfile;
const themePack = normalizeThemePack(
profile.themePack,
buildThemePackFromWorldProfile(baseExpandedProfile),
);
const storyGraph = normalizeWorldStoryGraph(
profile.storyGraph,
buildFallbackWorldStoryGraph(baseExpandedProfile, themePack),
);
const enrichedPlayableNpcs = playableNpcs.map((npc) => ({
...npc,
narrativeProfile: normalizeActorNarrativeProfile(
npc.narrativeProfile,
buildFallbackActorNarrativeProfile(npc, storyGraph, themePack),
),
}));
const enrichedStoryNpcs = storyNpcs.map((npc) => ({
...npc,
narrativeProfile: normalizeActorNarrativeProfile(
npc.narrativeProfile,
buildFallbackActorNarrativeProfile(npc, storyGraph, themePack),
),
}));
const landmarksWithResidues = landmarks.map((landmark) => ({
...landmark,
narrativeResidues:
landmark.narrativeResidues && landmark.narrativeResidues.length > 0
? landmark.narrativeResidues
: buildSceneNarrativeResidues({
sceneId: landmark.id,
sceneName: landmark.name,
profile: {
...baseExpandedProfile,
playableNpcs: enrichedPlayableNpcs,
storyNpcs: enrichedStoryNpcs,
storyGraph,
themePack,
},
}),
}));
const profileWithNarrative = {
...baseExpandedProfile,
playableNpcs: enrichedPlayableNpcs,
storyNpcs: enrichedStoryNpcs,
themePack,
storyGraph,
landmarks: landmarksWithResidues,
} satisfies CustomWorldProfile;
const knowledgeFacts =
profile.knowledgeFacts && profile.knowledgeFacts.length > 0
? profile.knowledgeFacts
: buildKnowledgeGraph(profileWithNarrative);
const threadContracts =
profile.threadContracts && profile.threadContracts.length > 0
? profile.threadContracts
: buildThreadContractsFromProfile(profileWithNarrative);
const compiledPacks = compileCampaignFromWorldProfile({
profile: {
...profileWithNarrative,
knowledgeFacts,
threadContracts,
},
});
registerScenarioPack(compiledPacks.scenarioPack);
return {
...profileWithNarrative,
knowledgeFacts,
threadContracts,
scenarioPackId: profile.scenarioPackId ?? compiledPacks.scenarioPack.id,
campaignPackId: profile.campaignPackId ?? compiledPacks.campaignPack.id,
};
}