Files
Genarrative/docs/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-30.md
高物 c49c64896a
Some checks failed
CI / verify (push) Has been cancelled
初始仓库迁移
2026-04-04 23:57:06 +08:00

13 KiB
Raw Blame History

工程优化审查报告2026-03-30

审查范围

  • 扫描范围:src/scripts/docs/.github/package.jsontsconfig.jsonvite.config.ts
  • 实际执行:npm run lintnpm run testnpm run buildnpm run check:content
  • 说明:按仓库要求,本报告不讨论中文乱码问题,只讨论工程结构、边界、质量门禁、可维护性和后续扩展成本

先说结论

这轮代码库相较 docs/ENGINEERING_OPTIMIZATION_REVIEW_2026-03-29.md 已经有明显进展,项目不再是“所有能力都糊在一个入口文件里”的状态了,但整体仍然处于“重构过渡期”。

已经落地的积极变化:

  • 入口路由已经从手写 pathname 分发,收敛到 src/main.tsx + src/routing/appRoutes.tsx
  • 持久化能力已经抽到 src/persistence/
  • 编辑器公共能力已经出现 src/editor/shared/
  • CI + ESLint + Prettier + Vitest 已经接入
  • 本地 API 插件已经从 vite.config.ts 抽走,落到 scripts/dev-server/localApiPlugins.ts
  • preview 环境里的 JSON 写入接口已经改成只读,这一点比上轮更安全

但当前仍然存在 5 个值得优先处理的工程问题:

  1. 运行时主链仍然过于集中,story/combat 的真实边界还没有彻底拆开
  2. src/services/ai.ts 仍处于迁移中间态,存在重复实现和旧逻辑残留
  3. 编辑器主入口仍是大型聚合组件,迁移残留没有清干净
  4. 质量门禁已经有框架但还不够“硬”warning 和测试覆盖缺口仍然明显
  5. 运行时渲染层和构建体积仍偏重,重 UI 模块还没拆到合适粒度

当前运行状态

  • npm run test 通过6 个测试文件共 18 个测试全部通过
  • npm run build 通过
  • npm run check:content 通过
  • npm run lint 通过,但仍有 76 条 warning

当前构建产物里仍然存在较重 chunk

  • dist/assets/GameCanvas-*.js346.58 kB
  • dist/assets/App-*.js326.89 kB
  • dist/assets/index-*.js197.80 kB
  • dist/assets/index-*.css117.37 kB

P0运行时主链仍然过于集中Story/Combat 边界还没有拆透

现状

虽然 App.tsx 已经明显瘦身,GameShell 也比之前更像壳层,但真正决定游戏推进的主逻辑仍然高度集中在两个大 hook 里:

  • src/hooks/useStoryGeneration.ts:824
  • src/hooks/useCombatFlow.ts:382

证据

useStoryGeneration 仍然同时编排了多个本应继续拆开的子领域:

  • src/hooks/useStoryGeneration.ts:852 接入 useCharacterChatFlow
  • src/hooks/useStoryGeneration.ts:1583 接入 useTreasureFlow
  • src/hooks/useStoryGeneration.ts:1588 接入 useInventoryFlow
  • src/hooks/useStoryGeneration.ts:1593 接入 useEquipmentFlow
  • src/hooks/useStoryGeneration.ts:1597 接入 useForgeFlow
  • 文件总长仍有约 3240
  • 结尾返回对象同时暴露剧情推进、地图旅行、NPC 交易/送礼/招募、角色聊天、背包与锻造 UI 能力,典型位置在 src/hooks/useStoryGeneration.ts:3171-3219

useCombatFlow 也不是纯计算层,它仍然同时承担:

  • 战斗前后状态推导
  • 动画播放与时间推进
  • setGameState 驱动的可视化编排
  • 逃跑流程与 story 响应同步

关键位置:

  • src/hooks/useCombatFlow.ts:382 useCombatFlow
  • src/hooks/useCombatFlow.ts:1195 playEscapeSequenceWithStorySync

影响

  • 任何一个“剧情选项新增”都很容易同时碰到 battle、npc、quest、inventory、chat 五条链路
  • review 成本高,回归范围判断依赖人脑上下文
  • 单测很难往 hook 级别补,因为副作用、异步节奏和 UI 状态混在一起
  • 后续想继续做 camp、custom world、更多 companion 玩法时,改动会继续集中到这两个入口

建议

  • useStoryGeneration 继续下钻成“剧情推进 orchestrator + 领域 action service”
  • useStoryGeneration 自己只保留编排,不再直接维护 trade/gift/recruit/chat/inventory/forge 的全部细节
  • useCombatFlow 继续向“纯战斗结算”和“播放适配层”分离
  • 先稳定公开接口,再做内部拆分,避免一次性大改

P1AI 服务迁移只完成了一半,src/services/ai.ts 仍然存在双轨实现

现状

仓库已经新增了:

  • src/services/llmClient.ts
  • src/services/llmParsers.ts
  • src/services/aiFallbacks.ts
  • src/services/aiTypes.ts

这说明拆层方向是对的。但 src/services/ai.ts 还没有真正变成“纯 orchestration 层”,里面仍然保留着一整套旧 transport / parse / fallback 逻辑。

证据

  • src/services/ai.ts:64-66 已经开始导入 llmClient
  • src/services/ai.ts:89-95 仍然保留本地 resolveTimeoutMs 和超时常量
  • src/services/ai.ts:647 仍然保留 _requestPlainTextCompletion
  • src/services/ai.ts:719 仍然保留 _parseJsonResponseText
  • src/services/ai.ts:739 仍然保留 _parseLineListContent
  • src/services/ai.ts:784 仍然保留 _streamPlainTextCompletion
  • src/services/ai.ts:885-904 仍然保留一批旧的 _buildOffline... helper

与之对应,新的实现已经在下面这些文件里存在:

  • src/services/llmClient.ts
  • src/services/llmParsers.ts

影响

  • 同一类能力现在有两套真相源后续改错误分类、超时策略、SSE 行为时容易漏改
  • 新同学读代码时很难判断应该继续改 ai.ts,还是应该去改 llmClient.ts
  • 迁移残留会拉高维护成本,也会让测试边界变得模糊

建议

  • src/services/ai.ts 收敛成“业务 prompt 编排 + fallback 选择”层
  • 彻底删掉未再需要的 _requestPlainTextCompletion_streamPlainTextCompletion_parse* 等旧 helper
  • 所有 transport / timeout / connectivity error / SSE 解析都只保留在 llmClient.tsllmParsers.ts
  • 迁移完成后,给 ai.ts 增加一组 orchestration 级测试,防止 fallback 分支回归

P1编辑器主入口仍然太重而且过渡态残留还在

现状

编辑器公共能力已经开始沉淀到 src/editor/shared/,这是好事;但主编辑器入口仍然比较重,且部分文件还保留着迁移过程里的死代码和注释块。

证据

PresetEditor 仍然是一个大型聚合组件:

  • src/components/PresetEditor.tsx:402 CharacterPresetPanel
  • src/components/PresetEditor.tsx:1174 SceneNpcPresetPanel
  • src/components/PresetEditor.tsx:1547 ScenePresetPanel
  • src/components/PresetEditor.tsx:1852 MonsterPresetPanel
  • src/components/PresetEditor.tsx:2218 PresetEditor
  • 文件总长仍有约 2279

同时,文件里还留着明显的过渡态残留:

  • src/components/PresetEditor.tsx:227 仍然保留未使用的 _SectionCard
  • src/components/NpcVisualEditor.tsx:684 保留 if (false) 的旧保存路径
  • src/components/NpcVisualEditor.tsx:685 明确写着 “Deprecated inline save path kept only until the shared client migration is cleaned up.”
  • src/components/NpcVisualEditor.tsx:724 还有第二处 if (false) 残留

影响

  • 编辑器后续继续扩展时,容易重新长回“大一统文件”
  • 过渡代码会误导维护者,以为旧保存链路仍然有效
  • 公共层虽然建起来了,但如果不清理旧代码,长期会形成“共享层 + 本地特例”并存

建议

  • 以“一个 tab 一个容器”的方式,把 PresetEditor 再拆一层
  • 清理 NpcVisualEditor 里的废弃代码块,不要再保留 if (false) 分支
  • 对编辑器共享层设定明确规则保存请求、克隆、Section 容器、错误提示都必须走 shared
  • 对编辑器做一次“小型迁移完成清扫”,优先删掉已经废弃但还挂在文件里的旧路径

P1质量门禁已经接上但还不够硬

现状

基础设施已经比上轮完整很多,但当前门禁仍然偏“有检查,不够严格”。

证据

当前 lint 结果:

  • 本次 npm run lint 实际输出 76 条 warning虽然命令返回成功

脚本和规则层面的原因也很明确:

  • package.json:12lint 仍然是 eslint . ... && tsc --noEmit,没有 --max-warnings 0
  • package.json:11lint:guardrails 虽然加了 --max-warnings 0,但它只覆盖一组显式 allowlist 文件
  • package.json:18check 会先跑 lint:guardrails,再跑宽松版 lint
  • .eslintrc.cjs:45-61 里大量规则仍然是 warn
  • .github/workflows/ci.yml:28-40 已经把 lint / guardrails / test / build / check:content 都接到 CI但 warning 仍能稳定进主干

测试覆盖也还是偏薄:

  • src/ 当前共有 126 个文件
  • 其中测试文件只有 6
  • 现有测试主要覆盖 routingpersistencejsonClientllmParsersbattlePlan
  • 关键主链如 useStoryGenerationuseCombatFlow 播放层、GameShell 集成链路、编辑器保存流程仍然没有直接测试

影响

  • 代码库会持续积累“已知 warning但先不处理”的债务
  • 工程信号会逐渐失真lint 通过不代表代码足够干净
  • 大 hook 和大组件的重构依然主要依赖人工回归

建议

  • 先把 warning 收敛到一个可控范围,再把全仓 lint 切成 --max-warnings 0
  • lint:guardrails 不要长期靠 allowlist应该逐步扩大到全仓
  • 优先补三类测试:
    • useStoryGeneration 的状态推进和 modal 决策
    • useCombatFlow 播放层的关键分支
    • 编辑器保存链路和覆盖数据回写

P2运行时渲染层仍然偏重chunk 也还没有拆到理想粒度

现状

入口已经有了 route lazy load模态框也做了一部分懒加载但核心运行时渲染层仍然比较重。

证据

  • src/components/AdventurePanel.tsx:470 导出主组件,文件总长约 1538
  • src/components/GameCanvas.tsx:472 导出主组件,文件总长约 1131
  • src/components/GameCanvas.tsx:768 仍然存在 false && companions.map(...) 的死分支
  • 本次构建里 GameCanvasApp 仍然是最大 chunk 之一

影响

  • 运行时页面的首屏与热区模块仍然偏重
  • 渲染逻辑、场景动画逻辑、实体选中逻辑继续堆在同一层review 和测试都偏吃力
  • 清理死分支前,维护者对“哪些渲染路径是真实生效的”判断成本更高

建议

  • GameCanvas 继续拆成 scene layer、entity layer、effect layer、overlay layer
  • AdventurePanel 继续下沉 quest/stats/settings/reward 子面板
  • 清理 false && 与未使用辅助组件,避免假分支继续留在主路径文件中
  • 结合真实 chunk 数据做一次 route 内部分包,而不是只靠入口级 lazy

P2TypeScript 安全基线仍然偏宽松

现状

当前类型拆分方向是好的,src/types.ts 已经退化成 barrel真实类型开始向 src/types/ 下沉。但 TypeScript 编译配置还比较保守,类型系统还没有真正变成强约束。

证据

  • tsconfig.json:12 skipLibCheck: true
  • tsconfig.json:16 allowJs: true
  • 当前没有启用 strict
  • 当前没有启用 noUncheckedIndexedAccess

影响

  • 对大对象和字典访问的保护仍然偏弱
  • 新模块迁移到更细类型后,收益会被宽松编译选项部分抵消
  • “代码能过类型检查”并不等于边界足够安全

建议

  • 不建议一次性全仓开严格模式
  • 可以先从 src/services/src/persistence/src/hooks/combat/ 这些相对纯的目录启更严格约束
  • 至少先评估开启 noUncheckedIndexedAccess 和减少 allowJs 的必要性

建议的落地顺序

第一阶段:先把过渡态清干净

  • 清理 ai.ts 的旧 transport / parser / fallback 实现
  • 清理 NpcVisualEditorGameCanvasPresetEditor 等文件里的 if (false)、未使用 helper、废弃注释块
  • 把 lint warning 数量先打下来

第二阶段:拆主链,不再让大 hook 继续膨胀

  • 继续拆 useStoryGeneration
  • 继续拆 useCombatFlow
  • 优先把“领域动作”和“播放/展示编排”分开

第三阶段:补门禁

  • 给主链补单测和少量集成 smoke
  • 让全仓 lint 朝 --max-warnings 0 收敛
  • 把 warning 从“长期存在”变成“短周期清零”

第四阶段:优化运行时体积

  • 细化 GameCanvasAdventurePanel 的模块边界
  • 按实际交互热区做 chunk 继续拆分
  • 用真实构建产物持续追踪是否降重

一句话结论

这轮仓库已经从“完全依赖大文件硬扛”进步到“基础设施开始成形”,但当前最需要做的已经不是继续加功能,而是把这轮重构收尾做完整:继续拆主链、删掉迁移残留、把 lint/test 门禁变硬、再顺手压缩运行时大模块。只要这一步补上,后续加剧情、加编辑器能力、加自定义世界都会轻很多。