Files
Genarrative/docs/audits/FUNCTION_DESIGN_AUDIT_2026-04-03.md
kdletters cbc27bad4a
Some checks failed
CI / verify (push) Has been cancelled
init with react+axum+spacetimedb
2026-04-26 18:06:23 +08:00

438 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 当前 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 越多,这类问题会越难排查。