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

@@ -14,6 +14,12 @@ import {requestChatMessageContent} from './llmClient';
import {parseJsonResponseText} from './llmParsers';
import {buildQuestIntentPrompt, QUEST_INTENT_SYSTEM_PROMPT} from './questPrompt';
import type {QuestIntent, QuestPreviewRequest} from './questTypes';
import {
buildFallbackActorNarrativeProfile,
normalizeActorNarrativeProfile,
} from './storyEngine/actorNarrativeProfile';
import { buildThemePackFromWorldProfile } from './storyEngine/themePack';
import { buildFallbackWorldStoryGraph } from './storyEngine/worldStoryGraph';
const QUEST_DIRECTOR_TIMEOUT_MS = 12000;
@@ -33,6 +39,41 @@ function coerceStringArray(value: unknown, fallback: string[]) {
return items.length > 0 ? items : fallback;
}
function resolveIssuerNarrativeProfile(
state: GameState,
encounter: Encounter,
) {
if (encounter.narrativeProfile) {
return encounter.narrativeProfile;
}
if (!state.customWorldProfile) {
return null;
}
const role =
state.customWorldProfile.storyNpcs.find((npc) =>
npc.id === encounter.id || npc.name === encounter.npcName,
)
?? state.customWorldProfile.playableNpcs.find((npc) =>
npc.id === encounter.id || npc.name === encounter.npcName,
);
if (!role) {
return null;
}
const themePack =
state.customWorldProfile.themePack
?? buildThemePackFromWorldProfile(state.customWorldProfile);
const storyGraph =
state.customWorldProfile.storyGraph
?? buildFallbackWorldStoryGraph(state.customWorldProfile, themePack);
return normalizeActorNarrativeProfile(
role.narrativeProfile,
buildFallbackActorNarrativeProfile(role, storyGraph, themePack),
);
}
function sanitizeQuestIntent(rawIntent: unknown, fallback: QuestIntent): QuestIntent {
if (!rawIntent || typeof rawIntent !== 'object') {
return fallback;
@@ -92,10 +133,12 @@ export function buildQuestGenerationContextFromState(params: {
const {state, encounter} = params;
const issuerNpcId = encounter.id ?? encounter.npcName;
const issuerState = state.npcStates[issuerNpcId];
const issuerNarrativeProfile = resolveIssuerNarrativeProfile(state, encounter);
return {
worldType: state.worldType,
customWorldProfile: state.customWorldProfile ?? null,
actState: state.storyEngineMemory?.actState ?? null,
currentSceneId: state.currentScenePreset?.id ?? null,
currentSceneName: state.currentScenePreset?.name ?? null,
currentSceneDescription: state.currentScenePreset?.description ?? null,
@@ -103,8 +146,13 @@ export function buildQuestGenerationContextFromState(params: {
issuerNpcName: encounter.npcName,
issuerNpcContext: encounter.context,
issuerAffinity: issuerState?.affinity ?? 0,
issuerNarrativeProfile,
issuerDisclosureStage: getNpcDisclosureStage(issuerState?.affinity ?? 0),
issuerWarmthStage: getNpcWarmthStage(issuerState?.affinity ?? 0),
activeThreadIds:
state.storyEngineMemory?.activeThreadIds?.slice(0, 4)
?? issuerNarrativeProfile?.relatedThreadIds?.slice(0, 4)
?? [],
encounterKind: encounter.kind ?? 'npc',
currentSceneTreasureHintCount: state.currentScenePreset?.treasureHints?.length ?? 0,
currentSceneHostileNpcIds: (state.currentScenePreset?.npcs ?? [])