fix: preserve rpg custom world detail profiles

This commit is contained in:
kdletters
2026-05-22 03:14:11 +08:00
parent a9d23a8a44
commit d74457faa2
19 changed files with 2726 additions and 109 deletions

View File

@@ -0,0 +1,157 @@
import type { CustomWorldProfile } from '../../types';
export function countCustomWorldProfileDetailSlots(
profile: Partial<CustomWorldProfile> | null | undefined,
) {
if (!profile) {
return 0;
}
return (
(profile.playableNpcs?.length ?? 0) +
(profile.storyNpcs?.length ?? 0) +
(profile.items?.length ?? 0) +
(profile.landmarks?.length ?? 0) +
(profile.sceneChapterBlueprints?.length ?? 0)
);
}
export function countCustomWorldProfileAssetSlots(
profile: Partial<CustomWorldProfile> | null | undefined,
) {
if (!profile) {
return 0;
}
return [
profile.cover?.imageSrc,
profile.openingCg?.storyboardImageSrc,
profile.openingCg?.videoSrc,
profile.openingCg?.posterImageSrc,
profile.camp?.imageSrc,
...(profile.playableNpcs ?? []).flatMap((role) => [
role.imageSrc,
role.generatedVisualAssetId,
role.generatedAnimationSetId,
...(role.skills ?? []).flatMap((skill) => [
skill.actionPreviewConfig?.basePath,
skill.actionPreviewConfig?.previewVideoPath,
skill.actionPreviewConfig?.file,
]),
...role.initialItems.flatMap((item) => [item.iconSrc]),
...Object.values(role.animationMap ?? {}).flatMap((config) => [
config?.basePath,
config?.previewVideoPath,
config?.file,
]),
]),
...(profile.storyNpcs ?? []).flatMap((npc) => [
npc.imageSrc,
npc.generatedVisualAssetId,
npc.generatedAnimationSetId,
...(npc.skills ?? []).flatMap((skill) => [
skill.actionPreviewConfig?.basePath,
skill.actionPreviewConfig?.previewVideoPath,
skill.actionPreviewConfig?.file,
]),
...npc.initialItems.flatMap((item) => [item.iconSrc]),
...Object.values(npc.animationMap ?? {}).flatMap((config) => [
config?.basePath,
config?.previewVideoPath,
config?.file,
]),
]),
...(profile.items ?? []).flatMap((item) => [item.iconSrc, item.sourcePath]),
...(profile.landmarks ?? []).map((landmark) => landmark.imageSrc),
...(profile.sceneChapterBlueprints ?? []).flatMap((chapter) =>
chapter.acts.flatMap((act) => [
act.backgroundImageSrc,
act.backgroundAssetId,
]),
),
].filter((value) => value?.trim()).length;
}
export function countCustomWorldProfileStructuredSlots(
profile: Partial<CustomWorldProfile> | null | undefined,
) {
if (!profile) {
return 0;
}
return [
profile.cover,
profile.attributeSchema,
profile.themePack,
profile.storyGraph,
profile.knowledgeFacts?.length ? profile.knowledgeFacts : null,
profile.threadContracts?.length ? profile.threadContracts : null,
profile.anchorContent,
profile.creatorIntent,
profile.anchorPack,
profile.lockState,
profile.ownedSettingLayers,
profile.generationMode,
profile.generationStatus,
profile.scenarioPackId,
profile.campaignPackId,
...(profile.playableNpcs ?? []).flatMap((role) => [
role.attributeProfile,
...(role.skills ?? []).map((skill) => skill.actionPreviewConfig),
...role.initialItems,
]),
...(profile.storyNpcs ?? []).flatMap((npc) => [
npc.attributeProfile,
...(npc.skills ?? []).map((skill) => skill.actionPreviewConfig),
...npc.initialItems,
]),
...(profile.landmarks ?? []).flatMap((landmark) => [
landmark.visualDescription,
landmark.narrativeResidues?.length ? landmark.narrativeResidues : null,
]),
...((profile.camp?.narrativeResidues ?? []).length
? [profile.camp?.narrativeResidues]
: []),
...(profile.sceneChapterBlueprints ?? []).flatMap((chapter) => [
chapter.sceneTaskDescription,
chapter.linkedThreadIds.length ? chapter.linkedThreadIds : null,
chapter.linkedLandmarkIds.length ? chapter.linkedLandmarkIds : null,
...chapter.acts.flatMap((act) => [
act.eventDescription,
act.linkedThreadIds.length ? act.linkedThreadIds : null,
act.actGoal,
act.transitionHook,
]),
]),
].filter(Boolean).length;
}
export function getCustomWorldProfileCompletenessScore(
profile: Partial<CustomWorldProfile> | null | undefined,
) {
return (
countCustomWorldProfileDetailSlots(profile) +
countCustomWorldProfileAssetSlots(profile) +
countCustomWorldProfileStructuredSlots(profile)
);
}
export function chooseMoreCompleteCustomWorldProfile(
fallbackProfile: CustomWorldProfile,
candidateProfile: CustomWorldProfile | null | undefined,
) {
if (!candidateProfile) {
return fallbackProfile;
}
if (candidateProfile.id !== fallbackProfile.id) {
return candidateProfile;
}
// 中文注释:发布 / 回读可能只返回列表摘要或旧快照。
// 同一个 profileId 下进入世界不能把当前结果页的封面、CG、角色资产降级掉。
return getCustomWorldProfileCompletenessScore(candidateProfile) >=
getCustomWorldProfileCompletenessScore(fallbackProfile)
? candidateProfile
: fallbackProfile;
}