@@ -39,6 +39,8 @@ import {
|
||||
KnowledgeFact,
|
||||
RoleAttributeProfile,
|
||||
SceneNarrativeResidue,
|
||||
SceneActBlueprint,
|
||||
SceneChapterBlueprint,
|
||||
ThemePack,
|
||||
ThreadContract,
|
||||
WorldStoryGraph,
|
||||
@@ -85,6 +87,18 @@ const CUSTOM_WORLD_NPC_VISUAL_GEAR_TYPES =
|
||||
'magic',
|
||||
'ranged',
|
||||
]);
|
||||
const SCENE_ACT_STAGES = new Set([
|
||||
'opening',
|
||||
'expansion',
|
||||
'turning_point',
|
||||
'climax',
|
||||
'aftermath',
|
||||
] as const);
|
||||
const SCENE_ACT_ADVANCE_RULES = new Set([
|
||||
'after_primary_contact',
|
||||
'after_active_step_complete',
|
||||
'after_chapter_resolution',
|
||||
] as const);
|
||||
const CUSTOM_WORLD_ROLE_ITEM_CATEGORIES = new Set([
|
||||
'武器',
|
||||
'护甲',
|
||||
@@ -892,6 +906,97 @@ function normalizeLandmarkDraft(
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSceneActStageCoverage(value: unknown) {
|
||||
const stageCoverage = Array.isArray(value)
|
||||
? value
|
||||
.filter((entry): entry is string => typeof entry === 'string')
|
||||
.map((entry) => entry.trim())
|
||||
.filter((entry): entry is SceneActBlueprint['stageCoverage'][number] =>
|
||||
SCENE_ACT_STAGES.has(entry as never),
|
||||
)
|
||||
: [];
|
||||
|
||||
return [...new Set(stageCoverage)];
|
||||
}
|
||||
|
||||
function normalizeSceneActBlueprint(
|
||||
value: unknown,
|
||||
index: number,
|
||||
sceneId: string,
|
||||
): SceneActBlueprint | null {
|
||||
if (!isRecord(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const encounterNpcIds = toStringArray(value.encounterNpcIds);
|
||||
const stageCoverage = normalizeSceneActStageCoverage(value.stageCoverage);
|
||||
const advanceRule = toText(value.advanceRule);
|
||||
const title = toText(value.title);
|
||||
const summary = toText(value.summary);
|
||||
|
||||
if (!title && !summary && encounterNpcIds.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
id: toText(value.id, `saved-scene-act-${sceneId}-${index + 1}`),
|
||||
sceneId,
|
||||
title: title || `第 ${index + 1} 幕`,
|
||||
summary: summary || title || `围绕${sceneId}继续推进`,
|
||||
stageCoverage:
|
||||
stageCoverage.length > 0
|
||||
? stageCoverage
|
||||
: index === 0
|
||||
? ['opening']
|
||||
: ['climax', 'aftermath'],
|
||||
backgroundImageSrc: toText(value.backgroundImageSrc) || undefined,
|
||||
encounterNpcIds,
|
||||
primaryNpcId: toText(value.primaryNpcId, encounterNpcIds[0] ?? ''),
|
||||
linkedThreadIds: toStringArray(value.linkedThreadIds),
|
||||
advanceRule: SCENE_ACT_ADVANCE_RULES.has(advanceRule as never)
|
||||
? (advanceRule as SceneActBlueprint['advanceRule'])
|
||||
: 'after_active_step_complete',
|
||||
actGoal: toText(value.actGoal),
|
||||
transitionHook: toText(value.transitionHook),
|
||||
};
|
||||
}
|
||||
|
||||
function normalizeSceneChapterBlueprints(value: unknown) {
|
||||
if (!Array.isArray(value)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const normalized = value
|
||||
.filter(isRecord)
|
||||
.map((entry, index) => {
|
||||
const sceneId = toText(entry.sceneId);
|
||||
if (!sceneId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const acts = Array.isArray(entry.acts)
|
||||
? entry.acts
|
||||
.map((act, actIndex) =>
|
||||
normalizeSceneActBlueprint(act, actIndex, sceneId),
|
||||
)
|
||||
.filter((act): act is SceneActBlueprint => Boolean(act))
|
||||
: [];
|
||||
|
||||
return {
|
||||
id: toText(entry.id, `saved-scene-chapter-${sceneId}-${index + 1}`),
|
||||
sceneId,
|
||||
title: toText(entry.title, toText(entry.sceneName, sceneId)),
|
||||
summary: toText(entry.summary),
|
||||
linkedThreadIds: toStringArray(entry.linkedThreadIds),
|
||||
linkedLandmarkIds: toStringArray(entry.linkedLandmarkIds),
|
||||
acts,
|
||||
} satisfies SceneChapterBlueprint;
|
||||
})
|
||||
.filter((entry): entry is SceneChapterBlueprint => Boolean(entry));
|
||||
|
||||
return normalized.length > 0 ? normalized : null;
|
||||
}
|
||||
|
||||
function normalizeProfile(value: unknown): CustomWorldProfile | null {
|
||||
if (!isRecord(value)) return null;
|
||||
|
||||
@@ -979,15 +1084,18 @@ function normalizeProfile(value: unknown): CustomWorldProfile | null {
|
||||
landmarks: landmarkDrafts,
|
||||
storyNpcs,
|
||||
}),
|
||||
themePack: preserveStructuredRecord<ThemePack>(value.themePack),
|
||||
storyGraph: preserveStructuredRecord<WorldStoryGraph>(value.storyGraph),
|
||||
knowledgeFacts:
|
||||
preserveStructuredRecordArray<KnowledgeFact>(value.knowledgeFacts),
|
||||
threadContracts:
|
||||
preserveStructuredRecordArray<ThreadContract>(value.threadContracts),
|
||||
anchorContent: preserveStructuredRecord<EightAnchorContent>(
|
||||
value.anchorContent,
|
||||
),
|
||||
themePack: preserveStructuredRecord<ThemePack>(value.themePack),
|
||||
storyGraph: preserveStructuredRecord<WorldStoryGraph>(value.storyGraph),
|
||||
knowledgeFacts:
|
||||
preserveStructuredRecordArray<KnowledgeFact>(value.knowledgeFacts),
|
||||
threadContracts:
|
||||
preserveStructuredRecordArray<ThreadContract>(value.threadContracts),
|
||||
sceneChapterBlueprints: normalizeSceneChapterBlueprints(
|
||||
value.sceneChapterBlueprints,
|
||||
),
|
||||
anchorContent: preserveStructuredRecord<EightAnchorContent>(
|
||||
value.anchorContent,
|
||||
),
|
||||
creatorIntent: normalizeCustomWorldCreatorIntent(value.creatorIntent),
|
||||
anchorPack:
|
||||
value.anchorPack && typeof value.anchorPack === 'object'
|
||||
|
||||
Reference in New Issue
Block a user