Files
Genarrative/docs/technical/RPG_OPENING_ACT_NPC_FLOW_ALIGNMENT_2026-04-30.md
2026-04-30 17:49:07 +08:00

5.4 KiB
Raw Permalink Blame History

RPG 开局首幕 NPC 流程收口方案2026-04-30

目标

本轮只收口“进入游戏开局场景后遇到第一幕第一批人”的运行时流程:

  1. 对方主角色好感度 >= 0 时,聊天过程中允许出现 npc_chat、任务、送礼、交易、获得帮助等 NPC 功能选项;聊天结束后界面只保留一个 story_continue_adventure,点击后直接推进到下一幕。
  2. 对方主角色好感度 < 0 时,聊天过程中只允许 npc_chat;聊天可以由模型中断,也可以由玩家主动中断。中断后只允许 npc_fightbattle_escape_breakout
  3. 删除这条主流程里的干扰分支:正好感聊天结束后不再展开旧 NPC 目录或相邻场景旅行;负好感聊天中不再混入交易、送礼、求助、任务、招募、切磋、离开等 function。

工程落点

  1. src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts
    • buildNpcChatFunctionOptionCatalog(...) 按当前 NPC 好感过滤功能候选。
    • 负好感聊天候选只保留 npc_chat
    • 正好感聊天结束后的 story_continue_adventure 只揭开下一幕入口;若当前场景没有下一幕,才退回相邻场景入口。
  2. src/hooks/rpg-runtime-story/choiceActions.ts
    • 应用 deferredRuntimeState.storyEngineMemory,保证点击继续后真正切到下一幕的 currentSceneActState
  3. server-rs/crates/api-server/src/runtime_story/compat/presentation.rs
    • 服务端 active NPC option catalog 与前端同规则对齐。
    • 负好感 active NPC 只返回 npc_chat
    • 非负好感 active NPC 返回聊天、帮助、交易、送礼、任务、招募等功能,不再返回战斗、切磋、离开。

验收

  1. 正好感 NPC 主动退出聊天后,只显示 story_continue_adventure
  2. 点击 story_continue_adventure 后,storyEngineMemory.currentSceneActState.currentActId 推进到下一幕。
  3. 负好感 NPC 聊天请求中的 functionOptions 为空,聊天 UI 不出现非聊天 function。
  4. 负好感聊天中断后只出现“战斗”和逃跑选项。
  5. 服务端 state catalog 对负好感 active NPC 不返回交易、送礼、帮助、任务、招募、切磋、离开等干扰入口。

2026-04-30 补充:负好感主动中止恢复

问题

敌对聊天的模型主动中止依赖后端建议 JSON 中的 shouldEndChat 字段,但部分入口没有把负好感 NPC 标记为 terminationMode: hostile_model,导致后端即使收到 shouldEndChat: true 也会按非敌对聊天忽略。另一个缺口是 NPC 主动开场第一轮只展示后续候选,没有处理 chatDirective.forceExit,因此第一轮开场也无法被模型主动中止。

落地

  1. src/hooks/rpg-runtime-story/useRpgRuntimeNpcInteraction.ts
    • 构造 NpcChatDirective 时直接读取当前 NPC 好感度。
    • 只要 affinity < 0,统一写入 limitReason: negative_affinityterminationMode: hostile_modelisHostileChat: true
    • NPC 主动开场收到 chatTurn.chatDirective.forceExit === true 时,立即收起 npcChatState,展示战斗与逃跑选项。

补充验收

  1. 任意负好感 NPC 聊天轮都必须向后端传 terminationMode: hostile_model,不能只依赖第一幕主 NPC 场景幕状态。
  2. 负好感 NPC 主动开场第一轮若返回 forceExit: true,聊天输入立即关闭,只显示战斗与逃跑。

2026-04-30 补充:聊天首句统一由模型 NPC 发起

问题

NPC 主动开场链路本身已经存在,并会以空玩家消息调用模型,同时传入 npcInitiatesConversation: true。但运行时入口曾把这条链路限制在 firstMeaningfulContactResolved !== true,导致角色完成首次有效接触后,再次从 NPC 入口或交互选项进入聊天时,会退回旧的 enterNpcChat(...) 本地入口:界面先展示玩家可点的话题,没有模型生成的 NPC 首句。负好感且非限定场景幕时,还存在一条本地敌对宣言分支,会直接给战斗/逃跑,绕过“先聊天再中断”的主流程。

落地

  1. enterNpcInteraction(...) 不再用 firstMeaningfulContactResolved 决定是否走 NPC 主动开场;只要是从 NPC 入口新开聊天,都调用 startNpcInitiatedOpening(...)
  2. handleNpcInteraction(...)chat 分支保留“当前已经在同一段 npcChatState 内时,点击 npc_chat 作为玩家回复”的行为;若不在已有聊天内,统一调用 startNpcInitiatedOpening(...)
  3. 删除负好感入口的本地敌对宣言分支。负好感只通过 NpcChatDirective 影响模型语气、功能选项和 forceExit 后的战斗/逃跑收束,不再跳过模型首句。
  4. enterNpcChat(...) 仅保留为缺少角色/世界类型或模型开场失败时的兜底入口,不作为正常聊天开场路径。

补充验收

  1. 不论好感度正负,也不论 firstMeaningfulContactResolved 是否为 true,新开聊天首轮都必须调用 streamNpcChatTurn(..., '', { npcInitiatesConversation: true })
  2. 新开聊天最终展示的第一条 dialogue 必须是模型返回的 NPC 文本,npcChatState.openingSource 必须是 npc_initiated
  3. 已经处于同一段 npcChatState 中时,点击 npc_chat 仍作为玩家本轮回复进入 handleNpcChatTurn(...),不能重新开一段 NPC 首句。
  4. 负好感入口不能直接显示本地战斗/逃跑;只有模型或玩家中断聊天后,才显示 npc_fightbattle_escape_breakout