438 lines
14 KiB
Markdown
438 lines
14 KiB
Markdown
# 当前 Function 设计审计(2026-04-03)
|
||
|
||
## 审计范围
|
||
|
||
本次审计重点阅读并对照了这些位置:
|
||
|
||
- `docs/experience/ADVENTURE_RUNTIME_DEV_EXPERIENCE.md`
|
||
- `docs/experience/PROJECT_WORK_EXPERIENCE_PLAYBOOK.md`
|
||
- `docs/experience/PROJECT_DEVELOPMENT_EXPERIENCE.md`
|
||
- `docs/audits/engineering/README.md`
|
||
- `src/data/stateFunctions.ts`
|
||
- `src/data/npcInteractions.ts`
|
||
- `src/data/treasureInteractions.ts`
|
||
- `src/hooks/useStoryGeneration.ts`
|
||
- `src/hooks/story/npcEncounterActions.ts`
|
||
- `src/hooks/story/npcInteraction.ts`
|
||
- `src/hooks/story/progressionActions.ts`
|
||
- `src/hooks/story/storyGenerationState.ts`
|
||
- `src/services/ai.ts`
|
||
- `src/services/prompt.ts`
|
||
- `src/components/AdventurePanel.tsx`
|
||
- `src/components/StateFunctionEditor.tsx`
|
||
|
||
## 先说结论
|
||
|
||
当前运行时的“function”不是单一体系,而是至少分成了 4 层:
|
||
|
||
1. `stateFunctions.ts` 里的基础战斗 / 空闲 function。
|
||
2. `npcInteractions.ts` 里的 NPC 交互 function。
|
||
3. `treasureInteractions.ts` 里的宝藏交互 function。
|
||
4. `useStoryGeneration.ts` / `npcInteraction.ts` / 背包装备锻造里额外加出来的“流程控制型 functionId”。
|
||
|
||
“选完选项触发 function 后没有触发剧情推理”这类现象,当前主要有 3 种来源:
|
||
|
||
1. **按设计先走本地分流,不会在第一次点击时立刻推理。**
|
||
2. **剧情推理其实已经完成,但被“继续冒险”这道 UI 中转门挡住了。**
|
||
3. **真正的可见性 bug:`story_continue_adventure` 的文案常量已经乱码,导致 UI 提示失效,用户很容易误判为没继续推理。**
|
||
|
||
---
|
||
|
||
## 1. 当前 function 体系分层
|
||
|
||
### 1.1 基础状态 function:`src/data/stateFunctions.ts`
|
||
|
||
这一层才是严格意义上的“状态 function 注册表”,由 `resolveFunctionOption` 统一解析。
|
||
|
||
当前运行时实际启用 12 个:
|
||
|
||
- 战斗类 7 个:
|
||
- `battle_all_in_crush`
|
||
- `battle_guard_break`
|
||
- `battle_probe_pressure`
|
||
- `battle_feint_step`
|
||
- `battle_recover_breath`
|
||
- `battle_finisher_window`
|
||
- `battle_escape_breakout`
|
||
- 空闲类 5 个:
|
||
- `idle_explore_forward`
|
||
- `idle_travel_next_scene`
|
||
- `idle_rest_focus`
|
||
- `idle_observe_signs`
|
||
- `idle_call_out`
|
||
|
||
关键设计点:
|
||
|
||
- 定义源头是 `BATTLE_FUNCTIONS` / `IDLE_FUNCTIONS`。
|
||
- 最终运行时集合由 `buildStateFunctionDefinitions` 产出。
|
||
- 可执行过滤走 `getExecutableFunctions`。
|
||
- 文案和视觉包装走 `resolveFunctionOption`。
|
||
- 默认选项池走 `getDefaultFunctionIdsForContext`。
|
||
|
||
注意:
|
||
|
||
- `idle_follow_clue` 仍然留在源码和 prompt 描述里,但在 `applyRuntimeFunctionAdjustments` 中被直接过滤掉,不会进入运行时 function 集合。
|
||
- `src/components/game-shell/useSceneTransitionModel.ts` 里仍然保留了 `idle_follow_clue` 的转场映射,这说明当前 function 清单并不完全一致。
|
||
|
||
### 1.2 NPC 交互 function:`src/data/npcInteractions.ts`
|
||
|
||
这一层不是通过 `resolveFunctionOption` 生成的,而是 `buildNpcEncounterStoryMoment` 直接拼出 `StoryOption`,并挂上 `interaction.kind = 'npc'`。
|
||
|
||
按功能类型看,当前会出现这些 `functionId`:
|
||
|
||
- `npc_preview_talk`
|
||
- `npc_trade`
|
||
- `npc_fight`
|
||
- `npc_spar`
|
||
- `npc_help`
|
||
- `npc_chat`(可重复出现 2 个以上,代表不同聊天话题)
|
||
- `npc_gift`
|
||
- `npc_recruit`
|
||
- `npc_quest_accept`
|
||
- `npc_quest_turn_in`
|
||
- `npc_leave`
|
||
|
||
关键设计点:
|
||
|
||
- 这一层的 function 是“眼前 NPC 交互目录”,不是基础状态机目录。
|
||
- 真正执行分流不在 `stateFunctions.ts`,而在 `handleNpcInteraction` / `resolveNpcInteractionDecision`。
|
||
- prompt 层通过 `availableOptions` 把这些 function 当作“固定可选项列表”交给模型,要求模型保留数量和 `functionId`。
|
||
|
||
### 1.3 宝藏交互 function:`src/data/treasureInteractions.ts`
|
||
|
||
这一层同样不是 `resolveFunctionOption` 体系,而是直接构造带 `interaction.kind = 'treasure'` 的选项。
|
||
|
||
当前有 3 个:
|
||
|
||
- `treasure_secure`
|
||
- `treasure_inspect`
|
||
- `treasure_leave`
|
||
|
||
执行时由 `useTreasureFlow.ts` 接管,最终再回到 `commitGeneratedState` 继续剧情推理。
|
||
|
||
### 1.4 流程控制 / 面板动作 functionId
|
||
|
||
这类 `functionId` 会进入 `lastFunctionId` 或 `commitGeneratedState`,但不在 `stateFunctions.ts` 注册表里:
|
||
|
||
- 流程控制:
|
||
- `story_continue_adventure`
|
||
- `camp_travel_home_scene`
|
||
- `story_opening_camp_dialogue`
|
||
- 面板动作:
|
||
- `inventory_use`
|
||
- `equipment_equip`
|
||
- `equipment_unequip`
|
||
- `forge_craft`
|
||
- `forge_dismantle`
|
||
- `forge_reforge`
|
||
|
||
这说明当前项目里“functionId”已经同时承担了 3 个角色:
|
||
|
||
- 运行时基础状态动作 ID
|
||
- NPC / 宝藏交互动作 ID
|
||
- 流程 / 面板事件 ID
|
||
|
||
这能跑,但可追踪性已经开始分裂。
|
||
|
||
---
|
||
|
||
## 2. 当前剧情推理触发链路
|
||
|
||
### 2.1 会立刻触发剧情推理的主链
|
||
|
||
这类点击后会直接走 AI 续推:
|
||
|
||
- 普通战斗 / 空闲 function
|
||
- `handleChoice`
|
||
- `buildResolvedChoiceState`
|
||
- `playResolvedChoice`
|
||
- `generateNextStep`
|
||
- 这些 NPC 交互
|
||
- `npc_preview_talk`
|
||
- `npc_help`
|
||
- `npc_chat`
|
||
- `npc_fight`
|
||
- `npc_spar`
|
||
- `npc_quest_accept`
|
||
- `npc_quest_turn_in`
|
||
- `npc_leave`
|
||
- 宝藏交互确认后
|
||
- `treasure_secure`
|
||
- `treasure_inspect`
|
||
- `treasure_leave`
|
||
- 面板动作确认后
|
||
- `inventory_use`
|
||
- `equipment_equip`
|
||
- `equipment_unequip`
|
||
- `forge_*`
|
||
|
||
共性是:
|
||
|
||
- 要么直接在 `handleChoice` 里调用 `generateNextStep`。
|
||
- 要么先走 `commitGeneratedState` / `commitGeneratedStateWithEncounterEntry`,再统一调用 `generateStoryForState`。
|
||
|
||
### 2.2 第一次点击不会立刻触发剧情推理的分流
|
||
|
||
这类最容易被误判成“function 触发了,但剧情没继续”:
|
||
|
||
- `npc_trade`
|
||
- `npc_gift`
|
||
- `npc_recruit`(队伍满时必进 modal;队伍未满时会先进招募对话流)
|
||
|
||
原因不是漏调,而是当前设计明确先走:
|
||
|
||
- `resolveNpcInteractionDecision`
|
||
- `trade_modal`
|
||
- `gift_modal`
|
||
- `recruit_modal`
|
||
- `recruit_immediate`
|
||
|
||
也就是说:
|
||
|
||
- 第一次点击只是**打开模态框 / 进入招募流程**。
|
||
- 真正推进剧情推理,要等到:
|
||
- `confirmTrade`
|
||
- `confirmGift`
|
||
- `confirmRecruit`
|
||
- `executeRecruitment`
|
||
|
||
如果产品预期是“用户每点一次选项就必须立即看到剧情继续”,这一层现在不满足。
|
||
|
||
### 2.3 `npc_chat` 是“先推理,再延迟展示选项”
|
||
|
||
`npc_chat` 不是普通的“生成下一幕 + 立刻给选项”,而是单独走这条链:
|
||
|
||
1. `commitNpcChatState` 先流式生成聊天正文。
|
||
2. 聊天结束后,再调用一次 `generateNextStep`。
|
||
3. 新一轮冒险选项不直接显示,而是塞进 `deferredOptions`。
|
||
4. 当前界面只先显示一个 `story_continue_adventure`。
|
||
5. 用户再点一次,才把 `deferredOptions` 放出来。
|
||
|
||
所以这里常见的误判是:
|
||
|
||
- **剧情推理其实已经做完了。**
|
||
- 只是 UI 先让用户“继续冒险”一次,才把新选项展示出来。
|
||
|
||
---
|
||
|
||
## 3. 本次排查发现的重点问题
|
||
|
||
### 3.1 真正最像“没触发剧情推理”的地方:模态框型 NPC function
|
||
|
||
定位:
|
||
|
||
- `src/hooks/story/npcEncounterActions.ts:427-449`
|
||
- `src/hooks/story/storyGenerationState.ts:41-80`
|
||
- `src/hooks/story/npcInteraction.ts:427-585`
|
||
|
||
现象:
|
||
|
||
- 点击 `npc_trade` / `npc_gift` / `npc_recruit` 后,故事文本区通常不会立刻变化。
|
||
- 当前故事也不会马上追加一段新的 `storyText`。
|
||
|
||
原因:
|
||
|
||
- 这些 function 的第一次点击只做本地 UI 分流。
|
||
- 直到用户在 modal 里确认,才会真正调用 `commitGeneratedState` 继续推理。
|
||
|
||
判断:
|
||
|
||
- **这不是单纯 bug,而是当前设计本身如此。**
|
||
- 但如果玩家从“选项即剧情推进”的心智出发,会非常像“点了没反应”。
|
||
|
||
建议优先级:高。
|
||
|
||
### 3.2 最关键的实际 bug:`story_continue_adventure` 文案乱码,导致“已推理但未显式提示”
|
||
|
||
定位:
|
||
|
||
- `src/hooks/useStoryGeneration.ts:92-96`
|
||
- `src/hooks/story/npcEncounterActions.ts:281-400`
|
||
- `src/components/AdventurePanel.tsx:534`
|
||
- `src/components/AdventurePanel.tsx:860`
|
||
- `src/components/AdventurePanel.tsx:879`
|
||
|
||
现象:
|
||
|
||
- `npc_chat` 完成后,系统本应展示一个“继续冒险”按钮,提示“剧情推理完成,继续后显示新的冒险选项”。
|
||
- 但当前 `CONTINUE_ADVENTURE_ACTION_TEXT` 常量已经写成了乱码:`缁х画鍐掗櫓`。
|
||
- `AdventurePanel` 又是靠 `option.actionText === '继续冒险'` 来识别这个特殊按钮。
|
||
|
||
直接后果:
|
||
|
||
- 特殊提示 UI 不会出现。
|
||
- 玩家只会看到一个普通且乱码的单选项。
|
||
- 实际上 `deferredOptions` 已经算好了,但用户极容易误会为“剧情没有继续推理”。
|
||
|
||
判断:
|
||
|
||
- **这是本次排查里最明确的实现级 bug。**
|
||
- 它不会阻止底层推理发生,但会严重破坏“推理已完成”的可见性。
|
||
|
||
建议优先级:最高。
|
||
|
||
### 3.3 `StateFunctionEditor` 覆盖不到真正最容易出问题的 function
|
||
|
||
定位:
|
||
|
||
- `src/components/StateFunctionEditor.tsx:13-21`
|
||
- `src/components/StateFunctionEditor.tsx:488`
|
||
- `src/components/StateFunctionEditor.tsx:772-838`
|
||
- `src/components/StateFunctionEditor.tsx:992-1211`
|
||
|
||
现象:
|
||
|
||
- 编辑器预览只接了 `buildStateFunctionDefinitions` / `getAllStateFunctionDefinitions` / `resolveFunctionOption`。
|
||
- 也就是它只能预览 `stateFunctions.ts` 那 12 个基础 function。
|
||
|
||
覆盖不到的关键分支:
|
||
|
||
- `npc_*`
|
||
- `treasure_*`
|
||
- `npc_preview_talk`
|
||
- `story_continue_adventure`
|
||
- modal 分流
|
||
- `npc_chat` 的 `deferredOptions`
|
||
|
||
判断:
|
||
|
||
- 这不是运行时 bug,但它解释了为什么这类问题很难在编辑器里提前暴露。
|
||
- 当前“Function 编辑器”并没有覆盖到最复杂、最容易让用户感知为异常的 function 链路。
|
||
|
||
建议优先级:高。
|
||
|
||
### 3.4 function 清单存在“源码有、运行时无、别处还在引用”的分裂
|
||
|
||
定位:
|
||
|
||
- `src/data/stateFunctions.ts:350`
|
||
- `src/data/stateFunctions.ts:429-441`
|
||
- `src/components/game-shell/useSceneTransitionModel.ts:19-25`
|
||
|
||
现象:
|
||
|
||
- `idle_follow_clue` 仍在定义表、提示词描述、若干 `switch` 分支中存在。
|
||
- 但最终在 `applyRuntimeFunctionAdjustments` 被过滤掉,不会进入运行时 function 集合。
|
||
- `useSceneTransitionModel` 里却还保留着它的转场模式映射。
|
||
|
||
判断:
|
||
|
||
- 这不是“没触发剧情推理”的直接原因。
|
||
- 但会让维护者误判当前真实 function 清单,也会增加后续继续扩 function 时的混乱。
|
||
|
||
建议优先级:中。
|
||
|
||
### 3.5 自动化测试几乎没有覆盖“最容易让人误会没推理”的链路
|
||
|
||
定位:
|
||
|
||
- 当前已有测试主要是 `src/hooks/story/storyGenerationState.test.ts`
|
||
|
||
已覆盖:
|
||
|
||
- `trade_modal`
|
||
- `recruit_modal`
|
||
- 地图切场景
|
||
|
||
未覆盖:
|
||
|
||
- `npc_chat` 的 `deferredOptions -> story_continue_adventure -> 真正显示新选项`
|
||
- `npc_trade` / `npc_gift` / `npc_recruit` 确认后是否一定调用 `commitGeneratedState`
|
||
- `story_continue_adventure` 文案与 UI 特判是否一致
|
||
- `npc_preview_talk -> enterNpcInteraction -> generateStoryForState`
|
||
- 宝藏分支确认后是否稳定续推
|
||
|
||
判断:
|
||
|
||
- 这类问题之所以能长期存在,一个核心原因就是**最关键的续推分支没有测试兜底**。
|
||
|
||
建议优先级:高。
|
||
|
||
---
|
||
|
||
## 4. 按“首次点击是否立即触发剧情推理”整理
|
||
|
||
### 4.1 首次点击就会继续推理
|
||
|
||
- `battle_*`
|
||
- `idle_*`
|
||
- `npc_preview_talk`
|
||
- `npc_help`
|
||
- `npc_chat`
|
||
- `npc_fight`
|
||
- `npc_spar`
|
||
- `npc_quest_accept`
|
||
- `npc_quest_turn_in`
|
||
- `npc_leave`
|
||
- `treasure_*`
|
||
- `inventory_use`
|
||
- `equipment_*`
|
||
- `forge_*`
|
||
|
||
### 4.2 首次点击不会立刻继续推理
|
||
|
||
- `npc_trade`
|
||
- `npc_gift`
|
||
- `npc_recruit`(至少会先进入确认 / 对话流)
|
||
- `story_continue_adventure`
|
||
|
||
这里要特别分清:
|
||
|
||
- `npc_trade` / `npc_gift` / `npc_recruit` 是**先分流,后确认,确认后再推理**。
|
||
- `story_continue_adventure` 是**推理已经完成,只是先把结果选项延后展示**。
|
||
|
||
---
|
||
|
||
## 5. 建议的修正顺序
|
||
|
||
### P0
|
||
|
||
- 修掉 `CONTINUE_ADVENTURE_ACTION_TEXT` 的乱码。
|
||
- `AdventurePanel` 不要再靠 `actionText === '继续冒险'` 判定特殊按钮,改成按 `functionId === 'story_continue_adventure'` 判定。
|
||
|
||
### P1
|
||
|
||
- 明确产品规则:
|
||
- `npc_trade` / `npc_gift` / `npc_recruit` 第一次点击是否就应该写入一条“进入交易 / 送礼 / 招募确认”的剧情反馈。
|
||
- 如果答案是“应该”,那这些 modal 型 function 需要补一层轻量 story feedback,而不是只弹框。
|
||
|
||
### P1
|
||
|
||
- 给 `npc_chat` 补自动化测试:
|
||
- 聊天后必须出现 `story_continue_adventure`
|
||
- 第二次点击后必须能展示 `deferredOptions`
|
||
|
||
### P1
|
||
|
||
- 给 `npc_trade` / `npc_gift` / `npc_recruit` 补自动化测试:
|
||
- 第一次点击只分流
|
||
- 确认后一定触发 `commitGeneratedState`
|
||
- 后续一定能拿到新的 `StoryMoment`
|
||
|
||
### P2
|
||
|
||
- 扩展 `StateFunctionEditor` 或补新的“交互 function 预览器”,把 `npc_*` / `treasure_*` / `story_continue_adventure` 也纳入可预演范围。
|
||
|
||
### P2
|
||
|
||
- 清理 `idle_follow_clue` 这类“半退场”的 function,保证:
|
||
- 运行时集合
|
||
- prompt 描述
|
||
- 编辑器
|
||
- 转场映射
|
||
- 测试
|
||
|
||
保持一致。
|
||
|
||
---
|
||
|
||
## 最后结论
|
||
|
||
当前最值得优先处理的,不是再继续加新的 function,而是先把这 3 个点收拢:
|
||
|
||
1. **把“首次点击只分流、不推理”的 function 明确标出来。**
|
||
2. **把 `npc_chat -> story_continue_adventure -> deferredOptions` 这条延迟展示链做清楚。**
|
||
3. **先修掉 `story_continue_adventure` 的乱码和基于文案的 UI 判断。**
|
||
|
||
如果不先处理这几个点,用户会持续把“按设计延迟”与“真实漏调剧情推理”混在一起感知,后面 function 越多,这类问题会越难排查。
|