import type { ActorNarrativeProfile, NpcDisclosureStage, SceneNarrativeDirective, VisibilitySlice, } from '../../types'; type BuildSceneNarrativeDirectiveParams = { sceneId?: string | null; sceneName?: string | null; encounterId?: string | null; encounterName?: string | null; recentActions?: string[] | null; activeThreadIds?: string[] | null; foregroundCarrierIds?: string[] | null; visibilitySlice?: VisibilitySlice | null; encounterNarrativeProfile?: ActorNarrativeProfile | null; disclosureStage?: NpcDisclosureStage | null; isFirstMeaningfulContact?: boolean; affinity?: number | null; }; function dedupeStrings(values: Array, limit = 6) { return [...new Set(values.map((value) => value?.trim() ?? '').filter(Boolean))] .slice(0, limit); } function resolveRevealBudget( disclosureStage?: NpcDisclosureStage | null, isFirstMeaningfulContact?: boolean, ) { if (isFirstMeaningfulContact || disclosureStage === 'guarded') { return 'low' as const; } if (disclosureStage === 'partial' || disclosureStage === 'honest') { return 'medium' as const; } return 'high' as const; } function resolveEmotionalCadence(params: BuildSceneNarrativeDirectiveParams) { if ((params.affinity ?? 0) < -10) { return 'hostile' as const; } if (params.isFirstMeaningfulContact) { return 'curious' as const; } if ((params.visibilitySlice?.forbiddenFactIds.length ?? 0) > 3) { return 'mysterious' as const; } if ((params.affinity ?? 0) >= 45) { return 'intimate' as const; } if ((params.recentActions ?? []).some((action) => /战|伤|逃|追|压/u.test(action))) { return 'tense' as const; } return 'curious' as const; } export function buildSceneNarrativeDirective( params: BuildSceneNarrativeDirectiveParams, ) { const primaryPressure = params.encounterNarrativeProfile?.immediatePressure || params.recentActions?.[0] || `${params.sceneName ?? '当前场景'}里仍有未被说透的动静。`; return { primaryPressure, activeThreadIds: dedupeStrings(params.activeThreadIds ?? [], 4), foregroundActorIds: dedupeStrings([ params.encounterId, params.encounterName, ], 3), foregroundCarrierIds: dedupeStrings(params.foregroundCarrierIds ?? [], 4), revealBudget: resolveRevealBudget( params.disclosureStage, params.isFirstMeaningfulContact, ), emotionalCadence: resolveEmotionalCadence(params), } satisfies SceneNarrativeDirective; }