import type { ChapterState, GameState, StorySignal, WorldMutation, } from '../../types'; function dedupeStrings(values: Array, limit = 6) { return [...new Set(values.map((value) => value?.trim() ?? '').filter(Boolean))] .slice(0, limit); } export function resolveWorldMutations(params: { state: GameState; signals: StorySignal[]; chapterState: ChapterState | null | undefined; }) { const currentSceneId = params.state.currentScenePreset?.id; const activeThreadIds = params.state.storyEngineMemory?.activeThreadIds ?? []; const mutations: WorldMutation[] = []; if (currentSceneId && params.chapterState) { mutations.push({ id: `mutation:scene:${currentSceneId}:${params.chapterState.stage}`, mutationType: 'scene_text', targetId: currentSceneId, reason: `${params.chapterState.title}正在改写这片地界的表面气氛。`, relatedThreadIds: params.chapterState.primaryThreadIds, }); } if (currentSceneId && params.signals.some((signal) => signal.signalType === 'win_battle')) { mutations.push({ id: `mutation:pressure:${currentSceneId}:battle`, mutationType: 'enemy_pressure', targetId: currentSceneId, reason: '这一带的敌意正在因交锋结果重新聚拢。', relatedThreadIds: dedupeStrings(activeThreadIds, 4), }); } if (params.signals.some((signal) => signal.signalType === 'obtain_carrier')) { mutations.push({ id: `mutation:attitude:${currentSceneId ?? 'scene'}:carrier`, mutationType: 'npc_attitude', targetId: currentSceneId ?? 'scene', reason: '关键载体已经落到你手里,相关角色的口风会开始变化。', relatedThreadIds: dedupeStrings(activeThreadIds, 4), }); } if (params.chapterState?.stage === 'climax' && currentSceneId) { mutations.push({ id: `mutation:route:${currentSceneId}:climax`, mutationType: 'route_unlock', targetId: currentSceneId, reason: '章节高潮逼近,新的通路或对峙点开始显影。', relatedThreadIds: params.chapterState.primaryThreadIds, }); } return mutations; } export function applyWorldMutationsToGameState(params: { state: GameState; mutations: WorldMutation[]; }) { const knownMutations = [ ...(params.state.storyEngineMemory?.worldMutations ?? []), ...params.mutations, ]; if (knownMutations.length <= 0) { return params.state; } const currentSceneId = params.state.currentScenePreset?.id ?? null; const relevantMutations = currentSceneId ? knownMutations.filter((mutation) => mutation.targetId === currentSceneId) : knownMutations; const latestSceneMutation = relevantMutations .filter((mutation) => mutation.mutationType === 'scene_text') .at(-1); const pressureMutationCount = relevantMutations.filter( (mutation) => mutation.mutationType === 'enemy_pressure', ).length; const attitudeMutation = relevantMutations .filter((mutation) => mutation.mutationType === 'npc_attitude') .at(-1); const currentPressureLevel = pressureMutationCount >= 3 ? 'extreme' : pressureMutationCount === 2 ? 'high' : pressureMutationCount === 1 ? 'medium' : params.state.currentScenePreset?.currentPressureLevel ?? 'low'; return { ...params.state, currentScenePreset: params.state.currentScenePreset ? { ...params.state.currentScenePreset, mutationStateText: [latestSceneMutation?.reason, attitudeMutation?.reason] .filter(Boolean) .join(' ') ?? params.state.currentScenePreset.mutationStateText ?? null, currentPressureLevel, description: [ params.state.currentScenePreset.description, latestSceneMutation?.reason, ] .filter(Boolean) .join(' '), npcs: params.state.currentScenePreset.npcs?.map((npc) => ({ ...npc, description: attitudeMutation && !npc.hostile ? `${npc.description} ${attitudeMutation.reason}` : npc.description, })), } : params.state.currentScenePreset, }; }