import type { CustomWorldProfile, StoryThread, ThreadContract, ThreadContractStep, } from '../../types'; function createStep( contractId: string, suffix: string, title: string, revealText: string, completionSignalIds: string[], optionalFactIds: string[], ) { return { id: `${contractId}:${suffix}`, title, revealText, completionSignalIds, optionalFactIds, } satisfies ThreadContractStep; } export function buildThreadContract(params: { thread: StoryThread; issuerActorId?: string | null; }) { const { thread, issuerActorId = thread.involvedActorIds[0] ?? null } = params; const contractId = `thread-contract:${thread.id}`; const narrativeType: ThreadContract['narrativeType'] = /调查|追查|真相|线索/u.test(`${thread.title} ${thread.summary}`) ? 'investigation' : /试炼|考验/u.test(`${thread.title} ${thread.summary}`) ? 'trial' : /关系|誓|旧识/u.test(`${thread.title} ${thread.summary}`) ? 'relationship' : /护送|撤离/u.test(`${thread.title} ${thread.summary}`) ? 'escort' : /夺回|收复|回收/u.test(`${thread.title} ${thread.summary}`) ? 'recovery' : 'investigation'; const primarySceneSignal = thread.relatedLocationIds[0] ? [`enter_scene:${thread.relatedLocationIds[0]}`, `inspect_scene:${thread.relatedLocationIds[0]}`] : [`talk_to_actor:${issuerActorId ?? 'actor'}`]; const actorSignal = issuerActorId ? [`talk_to_actor:${issuerActorId}`] : []; return { id: contractId, threadId: thread.id, issuerActorId, narrativeType, currentStepId: `${contractId}:step_1`, visibleStage: 0, steps: [ createStep( contractId, 'step_1', `追上 ${thread.title} 的表层线索`, thread.summary, primarySceneSignal, thread.relatedLocationIds.map((locationId) => `scene:${locationId}`), ), createStep( contractId, 'step_2', `把 ${thread.title} 的线头对回角色`, `${thread.stakes} 还需要有人当面松口。`, actorSignal.length > 0 ? actorSignal : [`resolve_contract_step:${thread.id}`], thread.involvedActorIds.map((actorId) => `actor:${actorId}`), ), ], followupThreadIds: [], } satisfies ThreadContract; } export function buildThreadContractsFromProfile(profile: CustomWorldProfile) { const threads = [ ...(profile.storyGraph?.visibleThreads ?? []), ...(profile.storyGraph?.hiddenThreads ?? []), ]; return threads.map((thread) => buildThreadContract({ thread, issuerActorId: thread.involvedActorIds[0] ?? null, }), ); }