Files
Genarrative/src/services/storyEngine/sceneNarrativeDirector.ts
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

83 lines
2.5 KiB
TypeScript

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<string | null | undefined>, 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;
}