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

@@ -1,4 +1,10 @@
import type {QuestGenerationContext} from '../services/aiTypes';
import {
buildFallbackActorNarrativeProfile,
normalizeActorNarrativeProfile,
} from '../services/storyEngine/actorNarrativeProfile';
import { buildThemePackFromWorldProfile } from '../services/storyEngine/themePack';
import { buildFallbackWorldStoryGraph } from '../services/storyEngine/worldStoryGraph';
import type {
EquipmentLoadout,
GameState,
@@ -78,12 +84,81 @@ function derivePlayerBuildGaps(playerBuildTags: string[]) {
.slice(0, 3);
}
function resolveRelatedNpcNarrativeProfile(params: {
customWorldProfile: GameState['customWorldProfile'];
encounter: GameState['currentEncounter'];
}) {
const { customWorldProfile, encounter } = params;
if (!customWorldProfile || !encounter || encounter.kind !== 'npc') {
return null;
}
const role =
customWorldProfile.storyNpcs.find((npc) =>
npc.id === encounter.id || npc.name === encounter.npcName,
)
?? customWorldProfile.playableNpcs.find((npc) =>
npc.id === encounter.id || npc.name === encounter.npcName,
);
if (!role) {
return encounter.narrativeProfile ?? null;
}
const themePack =
customWorldProfile.themePack ?? buildThemePackFromWorldProfile(customWorldProfile);
const storyGraph =
customWorldProfile.storyGraph
?? buildFallbackWorldStoryGraph(customWorldProfile, themePack);
return normalizeActorNarrativeProfile(
role.narrativeProfile,
buildFallbackActorNarrativeProfile(role, storyGraph, themePack),
);
}
function resolveActiveThreadIds(params: {
customWorldProfile: GameState['customWorldProfile'];
relatedNpcNarrativeProfile: RuntimeItemGenerationContext['relatedNpcNarrativeProfile'];
storyEngineMemory?: GameState['storyEngineMemory'] | QuestGenerationContext['activeThreadIds'];
}) {
const threadSource = params.storyEngineMemory;
if (Array.isArray(threadSource) && threadSource.length > 0) {
return threadSource.slice(0, 4);
}
if (
threadSource &&
!Array.isArray(threadSource) &&
threadSource.activeThreadIds?.length
) {
return threadSource.activeThreadIds.slice(0, 4);
}
if (params.relatedNpcNarrativeProfile?.relatedThreadIds.length) {
return params.relatedNpcNarrativeProfile.relatedThreadIds.slice(0, 4);
}
if (!params.customWorldProfile) {
return [];
}
const themePack =
params.customWorldProfile.themePack
?? buildThemePackFromWorldProfile(params.customWorldProfile);
const storyGraph =
params.customWorldProfile.storyGraph
?? buildFallbackWorldStoryGraph(params.customWorldProfile, themePack);
return storyGraph.visibleThreads.slice(0, 3).map((thread) => thread.id);
}
function buildBaseRuntimeContext(params: {
worldType: GameState['worldType'];
customWorldProfile: GameState['customWorldProfile'];
scene: Pick<ScenePresetInfo, 'id' | 'name' | 'description' | 'treasureHints'> | null;
encounter: GameState['currentEncounter'];
relatedNpcState: GameState['npcStates'][string] | null;
storyEngineMemory?: GameState['storyEngineMemory'] | QuestGenerationContext['activeThreadIds'];
storyHistory: GameState['storyHistory'];
playerCharacterId: string;
playerBuildTags: string[];
@@ -96,6 +171,7 @@ function buildBaseRuntimeContext(params: {
scene,
encounter,
relatedNpcState,
storyEngineMemory,
storyHistory,
playerCharacterId,
playerBuildTags,
@@ -103,6 +179,15 @@ function buildBaseRuntimeContext(params: {
generationChannel,
} = params;
const recentStoryLines = buildRecentStoryLines(storyHistory);
const relatedNpcNarrativeProfile = resolveRelatedNpcNarrativeProfile({
customWorldProfile,
encounter,
});
const activeThreadIds = resolveActiveThreadIds({
customWorldProfile,
relatedNpcNarrativeProfile,
storyEngineMemory,
});
return {
worldType,
@@ -117,9 +202,11 @@ function buildBaseRuntimeContext(params: {
encounterNpcName: encounter?.npcName ?? null,
encounterContextText: encounter?.context ?? null,
relatedNpcState,
relatedNpcNarrativeProfile,
relatedScene: scene,
recentStorySummary: buildRecentStorySummary(recentStoryLines),
recentActions: recentStoryLines,
activeThreadIds,
playerCharacterId,
playerBuildTags,
playerBuildGaps: derivePlayerBuildGaps(playerBuildTags),
@@ -146,6 +233,7 @@ export function buildLooseRuntimeItemGenerationContext(params: {
scene: params.scene ?? null,
encounter: params.encounter ?? null,
relatedNpcState: params.relatedNpcState ?? null,
storyEngineMemory: params.customWorldProfile?.storyGraph?.visibleThreads.map((thread) => thread.id) ?? [],
storyHistory: params.storyHistory ?? [],
playerCharacterId: params.playerCharacterId ?? 'runtime-loose-player',
playerBuildTags: params.playerBuildTags ?? [],
@@ -180,6 +268,7 @@ export function buildRuntimeItemGenerationContext(params: {
scene,
encounter,
relatedNpcState,
storyEngineMemory: state.storyEngineMemory,
storyHistory: state.storyHistory,
playerCharacterId: state.playerCharacter?.id ?? 'unknown-player',
playerBuildTags,
@@ -243,6 +332,7 @@ export function buildQuestRuntimeItemGenerationContext(params: {
recruited: false,
revealedFacts: [],
},
storyEngineMemory: context.activeThreadIds,
storyHistory: context.recentStoryMoments ?? [],
playerCharacterId: context.playerCharacter?.id ?? 'quest-player',
playerBuildTags,