import type { CustomWorldProfile } from '../../types'; export function countCustomWorldProfileDetailSlots( profile: Partial | 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 | 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 | 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 | 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; }