5.4 KiB
5.4 KiB
RPG 开局首幕 NPC 流程收口方案(2026-04-30)
目标
本轮只收口“进入游戏开局场景后遇到第一幕第一批人”的运行时流程:
- 对方主角色好感度
>= 0时,聊天过程中允许出现npc_chat、任务、送礼、交易、获得帮助等 NPC 功能选项;聊天结束后界面只保留一个story_continue_adventure,点击后直接推进到下一幕。 - 对方主角色好感度
< 0时,聊天过程中只允许npc_chat;聊天可以由模型中断,也可以由玩家主动中断。中断后只允许npc_fight与battle_escape_breakout。 - 删除这条主流程里的干扰分支:正好感聊天结束后不再展开旧 NPC 目录或相邻场景旅行;负好感聊天中不再混入交易、送礼、求助、任务、招募、切磋、离开等 function。
工程落点
src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.tsbuildNpcChatFunctionOptionCatalog(...)按当前 NPC 好感过滤功能候选。- 负好感聊天候选只保留
npc_chat。 - 正好感聊天结束后的
story_continue_adventure只揭开下一幕入口;若当前场景没有下一幕,才退回相邻场景入口。
src/hooks/rpg-runtime-story/choiceActions.ts- 应用
deferredRuntimeState.storyEngineMemory,保证点击继续后真正切到下一幕的currentSceneActState。
- 应用
server-rs/crates/api-server/src/runtime_story/compat/presentation.rs- 服务端 active NPC option catalog 与前端同规则对齐。
- 负好感 active NPC 只返回
npc_chat。 - 非负好感 active NPC 返回聊天、帮助、交易、送礼、任务、招募等功能,不再返回战斗、切磋、离开。
验收
- 正好感 NPC 主动退出聊天后,只显示
story_continue_adventure。 - 点击
story_continue_adventure后,storyEngineMemory.currentSceneActState.currentActId推进到下一幕。 - 负好感 NPC 聊天请求中的
functionOptions为空,聊天 UI 不出现非聊天 function。 - 负好感聊天中断后只出现“战斗”和逃跑选项。
- 服务端 state catalog 对负好感 active NPC 不返回交易、送礼、帮助、任务、招募、切磋、离开等干扰入口。
2026-04-30 补充:负好感主动中止恢复
问题
敌对聊天的模型主动中止依赖后端建议 JSON 中的 shouldEndChat 字段,但部分入口没有把负好感 NPC 标记为 terminationMode: hostile_model,导致后端即使收到 shouldEndChat: true 也会按非敌对聊天忽略。另一个缺口是 NPC 主动开场第一轮只展示后续候选,没有处理 chatDirective.forceExit,因此第一轮开场也无法被模型主动中止。
落地
src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts- 构造
NpcChatDirective时直接读取当前 NPC 好感度。 - 只要
affinity < 0,统一写入limitReason: negative_affinity、terminationMode: hostile_model、isHostileChat: true。 - NPC 主动开场收到
chatTurn.chatDirective.forceExit === true时,立即收起npcChatState,展示战斗与逃跑选项。
- 构造
补充验收
- 任意负好感 NPC 聊天轮都必须向后端传
terminationMode: hostile_model,不能只依赖第一幕主 NPC 场景幕状态。 - 负好感 NPC 主动开场第一轮若返回
forceExit: true,聊天输入立即关闭,只显示战斗与逃跑。
2026-04-30 补充:聊天首句统一由模型 NPC 发起
问题
NPC 主动开场链路本身已经存在,并会以空玩家消息调用模型,同时传入 npcInitiatesConversation: true。但运行时入口曾把这条链路限制在 firstMeaningfulContactResolved !== true,导致角色完成首次有效接触后,再次从 NPC 入口或交互选项进入聊天时,会退回旧的 enterNpcChat(...) 本地入口:界面先展示玩家可点的话题,没有模型生成的 NPC 首句。负好感且非限定场景幕时,还存在一条本地敌对宣言分支,会直接给战斗/逃跑,绕过“先聊天再中断”的主流程。
落地
enterNpcInteraction(...)不再用firstMeaningfulContactResolved决定是否走 NPC 主动开场;只要是从 NPC 入口新开聊天,都调用startNpcInitiatedOpening(...)。handleNpcInteraction(...)的chat分支保留“当前已经在同一段npcChatState内时,点击npc_chat作为玩家回复”的行为;若不在已有聊天内,统一调用startNpcInitiatedOpening(...)。- 删除负好感入口的本地敌对宣言分支。负好感只通过
NpcChatDirective影响模型语气、功能选项和forceExit后的战斗/逃跑收束,不再跳过模型首句。 enterNpcChat(...)仅保留为缺少角色/世界类型或模型开场失败时的兜底入口,不作为正常聊天开场路径。
补充验收
- 不论好感度正负,也不论
firstMeaningfulContactResolved是否为true,新开聊天首轮都必须调用streamNpcChatTurn(..., '', { npcInitiatesConversation: true })。 - 新开聊天最终展示的第一条
dialogue必须是模型返回的 NPC 文本,npcChatState.openingSource必须是npc_initiated。 - 已经处于同一段
npcChatState中时,点击npc_chat仍作为玩家本轮回复进入handleNpcChatTurn(...),不能重新开一段 NPC 首句。 - 负好感入口不能直接显示本地战斗/逃跑;只有模型或玩家中断聊天后,才显示
npc_fight与battle_escape_breakout。