13 KiB
M4 Runtime Story Rust 文件拆分方案(2026-04-22)
更新时间:2026-04-22
0. 文档目标
本文件只解决一个工程问题:
把 server-rs/crates/api-server/src/runtime_story.rs 从当前超大单文件拆成可维护的 Rust 子模块,同时不改变既有 M4 compat bridge 的行为边界。
当前 runtime_story.rs 已超过 7000 行,内部同时混杂了:
- Axum route handler
- snapshot 持久化与 DTO 组装
- runtime story compat 动作结算
- runtime option compiler / currentStory builder
- LLM 文本增强
- test fixture 与 route boundary 回归
这已经超出单文件可维护范围,也会直接拖慢后续继续迁移 Node compat 分支的速度。
1. 本轮拆分原则
本轮拆分坚持以下边界:
- 先拆“展示编译层”和“AI 增强层”,不先重写规则结算层。
- 不改变
app.rs里的路由绑定函数名。 - 不改变
RuntimeStoryActionResponse / RuntimeStoryAiResponsecontract。 - 不改变现有 compat bridge 的动作规则、patch、snapshot 写回顺序。
- 优先做可验证的文件拆分,不把这轮演变成架构重写。
原因:
- 当前
resolve_runtime_story_choice_action(...)仍在持续迁移 Node compat 行为,短期内继续集中在主文件更利于快速补链。 presentation / option compiler / dialogue currentStory / AI payload对外依赖相对单纯,更适合先抽走。- test module 独立后,可以明显降低主文件噪音,后续再继续拆规则层也更安全。
2. 首轮拆分目标
首轮只拆以下 3 块:
2.1 runtime_story/presentation.rs
职责:
viewModel编译availableOptions编译currentStorybuilderdialogue / pendingQuestOffer的 story shape helperstory option与interaction的组装
这块包含但不限于:
build_runtime_story_view_modelbuild_runtime_story_optionsbuild_fallback_runtime_story_optionsbuild_dialogue_current_storybuild_pending_quest_offer_storybuild_story_option_from_runtime_option
2.2 runtime_story/ai.rs
职责:
initial / continue的RuntimeStoryAiResponseactions/resolve后的最小 LLM 文本增强- 对话 turn 解析
- AI prompt payload 构造
这块包含但不限于:
build_runtime_story_ai_responsegenerate_ai_story_textgenerate_action_story_payloadgenerate_npc_dialogue_payloadgenerate_reasoned_story_payloadparse_dialogue_turns
2.3 runtime_story/tests.rs
职责:
- route boundary test
- 纯函数回归
- fixture builder
- 鉴权 token helper
3. 拆分后目录形态
首轮目标目录:
server-rs/crates/api-server/src/
├─ runtime_story.rs
└─ runtime_story/
├─ ai.rs
├─ presentation.rs
└─ tests.rs
其中:
runtime_story.rs保留为外层入口模块- 子模块通过
mod ai; mod presentation; #[cfg(test)] mod tests;组织 runtime_story.rs继续暴露原有 5 个 route handler:resolve_runtime_story_stateget_runtime_story_stateresolve_runtime_story_actiongenerate_runtime_story_initialgenerate_runtime_story_continue
4. Rust 侧实现策略
4.1 不做新的共享 crate
本轮不把 helper 再抽成新的 crate 或全局 util module。
原因:
- 当前拆分目标是降低单文件复杂度,不是扩展跨模块复用面。
presentation / ai / tests仍强依赖runtime_story内部 helper。- 如果过早抽到 crate 级共享层,会额外引入新的 API 稳定面和更大改动范围。
4.2 子模块通过 super::* 复用内部 helper
首轮允许子模块继续通过 use super::*; 访问现有内部函数、结构体和常量。
这是刻意的折中:
- 优先完成物理拆分
- 暂不要求所有 helper 立即彻底分层
- 后续再在第二轮继续把规则层和 state helper 往下切
4.3 第二轮候选拆分
本轮完成后,下一轮可继续评估:
runtime_story/actions.rsruntime_story/battle.rsruntime_story/inventory.rsruntime_story/npc_state.rsruntime_story/json_state.rs
但这些都不属于本次提交的必达范围。
5. 验证要求
拆分后至少必须通过:
cargo test -p api-server runtime_story --manifest-path D:\\Genarrative\\server-rs\\Cargo.tomlcargo check -p api-server --manifest-path D:\\Genarrative\\server-rs\\Cargo.tomlnode D:\\Genarrative\\scripts\\check-encoding.mjs
若以上任一失败,则本轮拆分不算完成。
6. 本轮明确不做
- 不改 compat bridge 业务规则
- 不新增或删除 runtime functionId
- 不顺手把 quest 里的历史
inspect_treasure字段一并清理 - 不提前把
resolve_story_action / sync_runtime_snapshot_projection真相 reducer 并入本轮 - 不修改前端调用边界
7. 完成标记
本轮拆分完成的判定标准:
runtime_story.rs明显缩短,至少不再携带 tests 与 AI/presentation 全量实现runtime_story/ai.rs、runtime_story/presentation.rs、runtime_story/tests.rs已落地- route handler 对外签名不变
- 定向回归全部通过
达到以上条件后,再继续进入下一轮“规则层进一步拆分”。
8. 2026-04-22 实际落地进度
截至 2026-04-22 当前工作区,首轮物理拆分已经进入可继续演进状态:
- 外层入口 runtime_story.rs 已缩成薄壳,只保留原有 5 个 route handler 的导出。
- 兼容实现主体已迁入 compat.rs,并继续保留规则结算主链。
tests已外置到 tests.rs,避免继续堆在主文件内。- 本轮进一步把
compat内部再拆成: - 当前拆分策略仍然维持
compat内部模块,通过use super::*;复用共享 helper,不提前抽独立 crate。 - quest replace / fixture 中原本残留的
inspect_treasuremock 已同步替换为更中性的talk_to_npc,避免把已废弃的 treasure 概念继续固化进新模块。
下一步不再是继续把文件塞回去,而是沿着当前目录继续把“无 HTTP / 无 AppState”的纯规则与编译逻辑收敛出来,为后续独立 crate 做第二阶段准备。
9. 第二阶段收敛边界
第二阶段不新增对外入口,只继续整理 compat 内部依赖面:
- 继续保留 compat.rs 作为 route handler、快照持久化与 compat action orchestration 的主入口。
- 优先把“只依赖
serde_json::Value/ 共享 contract / 纯函数 helper”的部分抽到内部纯模块。 - 当前最适合先抽的块不是 battle route,而是:
- NPC 状态补齐
- encounter / inventory / equipment 读写
- quest / trade / recruit 等会复用的
game_state纯变换 helper
- 这一步的目标不是立刻独立 crate,而是先在
api-server内形成清晰的“HTTP 外壳”与“纯状态编译层”分界。
如果第二阶段完成后 compat 内已经能明显区分:
AppState / RequestContext / Axum相关边界Value -> Value / DTO的纯规则层
那么第三阶段再把后者抽成独立 crate,风险会显著低于现在直接新建 crate。
10. 第二阶段 battle 收敛进度
截至 2026-04-22 当前工作区,第二阶段已经继续向“纯规则内聚”推进一块 battle 逻辑:
compat新增 battle.rs,专门承接 battle 兼容桥里的纯规则与展示编译 helper。- 已迁入
battle.rs的内容包括:- 战斗数值写回 helper
- 技能 / 物品的 battle action plan 生成
- 战斗技能冷却读写
- battle 选项与推荐物品编译
- battle 胜利经验奖励计算
- compat.rs 当前继续只保留
resolve_battle_action(...)这种动作编排入口,不再堆放大段 battle 纯 helper。 - core.rs 中原本只服务 battle 链的 skill / inventory 读取与 cooldown helper 已同步移出,避免“纯规则仍散落在多个模块”。
- 这一步仍然没有改变:
- Axum route handler 签名
AppState / RequestContext边界RuntimeStoryActionResponse/ patch / snapshot 的写回顺序
这说明第二阶段已经不只是在“补状态 helper”,而是开始把 compat 内最独立的一类规则块真正收束成内部纯模块。下一步可以继续沿同样方法处理 forge,以及 trade / gift / companion 这类不依赖 HTTP 的 helper 群。
同日进一步推进后,这条路线已经从 battle 扩展到 forge:
compat新增 forge.rs,把锻造配方、重铸成本、材料消耗、运行时物品生成、拆解产物和重铸产物构造统一收口。- compat.rs 当前对 forge 只保留:
resolve_forge_craft_action(...)resolve_forge_dismantle_action(...)resolve_forge_reforge_action(...)这些动作编排入口。
- game_state.rs 里的 NPC trade bootstrapping 继续直接复用
forge.rs中的运行时物品构造 helper,避免 trade stock 与工坊产物出现两套生成规则。 - 这意味着第二阶段已经形成一个更清楚的内部形态:
battle.rs:战斗纯规则与战斗选项编译forge.rs:工坊纯规则与运行时锻造物品生成game_state.rs:快照态读写与 NPC / inventory / equipment 状态桥
后续再继续迁 trade / gift / companion 时,目标就不再是单纯减少行数,而是把 compat bridge 逐步收束成“动作编排壳 + 多个纯规则模块”的明确结构。
在此基础上,同日又继续把 NPC 交互侧的一批纯 helper 收到独立模块:
compat新增 npc_support.rs。- 已迁入的内容包括:
- 赠礼好感收益与赠礼结果文本
- 交易价格、折扣档位、货币文本、数量后缀
- 队伍招募与满员换队 helper
- compat.rs 现在对
npc_trade / npc_gift / npc_recruit仍只保留动作编排,不再承担底层价格计算和队伍变换逻辑。 - 到这一步,
compat.rs的主要职责已经更接近:- route handler / snapshot bridge
- action orchestration
- 少量尚未迁出的共享 glue code
这为后续把“无 HTTP / 无 AppState”的剩余 glue code 再往下收,提供了更明确的拆分方向。
第二阶段继续推进到 action resolver 编排后,当前又新增动作编排模块:
已迁入的内容包括:
battle_*equipment_equip / equipment_unequipforge_craft / forge_dismantle / forge_reforgenpc_preview_talk / npc_chat / npc_help / npc_fight / npc_sparnpc_trade / npc_gift / npc_recruitnpc_chat_quest_offer_viewnpc_chat_quest_offer_replacenpc_chat_quest_offer_abandonnpc_quest_acceptnpc_quest_turn_in
这组 resolver 虽然仍是 action orchestration,但已经不依赖 HTTP / AppState,只依赖快照 Value、当前故事 currentStory、共享 DTO 与内部 helper,因此适合先作为 api-server 内部模块沉淀。
迁移后 compat.rs 对这些动作只保留 functionId 分发、快照桥接与少量共享 glue code,不再承载 battle / equipment / forge / NPC / quest 的具体结算细节。