import { buildRuntimeItemAiIntent, buildRuntimeItemAiPromptInput, } from '../data/runtimeItemNarrative'; import type { RuntimeItemGenerationContext, RuntimeItemPlan, RuntimeRelationAnchor, } from '../types'; import { buildRuntimeItemStoryFingerprint } from '../services/storyEngine/carrierNarrativeCompiler'; import { buildCarrierVisibilitySlice } from '../services/storyEngine/visibilityEngine'; function describeRelationAnchor(anchor: RuntimeRelationAnchor) { switch (anchor.type) { case 'npc': return `NPC:${anchor.npcName}`; case 'scene': return `场景:${anchor.sceneName}`; case 'monster': return `怪物:${anchor.monsterName}`; case 'quest': return `任务:${anchor.questName}`; case 'faction': return `势力:${anchor.factionName}`; default: return `地标:${anchor.landmarkName}`; } } function describeCarrierFactId(factId: string) { if (factId === 'visibleClue') return '可见线索'; if (factId === 'currentAppearanceReason') return '当前出现理由'; if (factId === 'witnessMark') return '见证痕'; if (factId === 'unresolvedQuestion') return '未完成问题'; if (factId.startsWith('thread:')) return `线程索引(${factId.slice('thread:'.length)})`; return factId; } function describePlan( context: RuntimeItemGenerationContext, plan: RuntimeItemPlan, index: number, ) { const promptInput = buildRuntimeItemAiPromptInput(context, plan); const fallbackIntent = buildRuntimeItemAiIntent(context, plan); const fallbackFingerprint = buildRuntimeItemStoryFingerprint({ context, plan, intent: fallbackIntent, }); const visibilitySlice = buildCarrierVisibilitySlice({ activeThreadIds: context.activeThreadIds, storyFingerprint: fallbackFingerprint, }); return [ `物品 ${index + 1}`, `- slot: ${plan.slot}`, `- 物品类型: ${promptInput.desiredItemKind}`, `- 持续性: ${promptInput.permanence}`, `- 关系锚点: ${describeRelationAnchor(plan.relationAnchor)}`, `- 世界摘要: ${promptInput.worldSummary}`, `- 场景摘要: ${promptInput.sceneSummary || '无'}`, `- 遭遇摘要: ${promptInput.encounterSummary || '无'}`, `- 相关人物: ${promptInput.relatedNpcSummary}`, `- 当前激活线程: ${promptInput.activeThreadSummary || '暂无'}`, `- 近期剧情: ${promptInput.recentStorySummary}`, `- 玩家当前 build: ${promptInput.playerBuildDirection.join('、') || '均衡'}`, `- 玩家待补缺口: ${promptInput.playerBuildGaps.join('、') || '无明显缺口'}`, `- 本次目标方向: ${plan.targetBuildDirection.join('、') || '均衡'}`, `- 物件可直写线索: ${visibilitySlice.sayableFactIds.map(describeCarrierFactId).join('、') || '无'}`, `- 物件只宜暗写: ${visibilitySlice.inferredFactIds.map(describeCarrierFactId).join('、') || '无'}`, ].join('\n'); } export const RUNTIME_ITEM_INTENT_SYSTEM_PROMPT = `你是 AI 原生叙事 RPG 的运行时物品导演。 你只返回 JSON,不要输出 Markdown、解释或代码块。 输出结构: { "intents": [ { "shortNameSeed": "中文短种子", "sourcePhrase": "中文来源短语", "reasonToAppear": "中文出现理由", "relationHooks": ["中文关系钩子"], "desiredBuildTags": ["中文 build 标签"], "desiredFunctionalBias": ["heal|mana|cooldown|guard|damage"], "tone": "grim|mysterious|martial|ritual|survival", "visibleClue": "玩家第一眼能抓到的痕迹", "witnessMark": "它见证过什么的使用痕", "unfinishedBusiness": "背后仍未结清的问题", "hiddenHook": "更深一层但别直接讲穿的钩子", "reactionHooks": ["以后谁会对它起反应"], "namingPattern": "命名范式建议" } ] } 规则: - intents 数量必须与输入物品数量完全一致,顺序也必须一致。 - 所有自然语言字段都必须使用中文。 - 物品意图必须贴合当前场景、关系锚点、近期剧情和玩家当前 build。 - visibleClue / witnessMark / unfinishedBusiness / hiddenHook 要优先围绕当前激活线程、相关角色压力和旧痕来写。 - desiredBuildTags 要优先围绕玩家当前 build 与待补缺口,不要写空数组。 - desiredFunctionalBias 只能从给定枚举里选 1 到 2 个。 - reasonToAppear 必须解释为什么这件东西会在当前局势里出现,而不是泛泛描述。`; export function buildRuntimeItemIntentPrompt(params: { context: RuntimeItemGenerationContext; plans: RuntimeItemPlan[]; }) { return [ `生成渠道:${params.context.generationChannel}`, `以下每个物品都需要给出一条可编译的运行时物品意图。`, ...params.plans.map((plan, index) => describePlan(params.context, plan, index)), '请严格返回 JSON。', ].join('\n\n'); }