[Code Review] 整体代码质量、架构缺陷与技术债务全景审查报告 #7

Closed
opened 2026-09-14 06:57:54 +00:00 by troytt · 2 comments
Owner

项目全景代码质量与架构审查报告

经过对 troytt/diablo2-web 仓库全模块(MPQ 容器、各二进制格式解码器、游戏模拟子系统、WebGL2 渲染器、锁步网络层、场景与打包管线)的深度静态分析、逻辑审计与验证套件执行,现将项目的整体质量评价、核心缺陷与高危风险、技术债务及演进重构建议汇总如下。


一、整体质量与工程亮点

  1. 零运行时外部依赖(Pure TS + WebGL2)
    • 运行时完全摆脱重量级游戏引擎(如 Pixi.js / Phaser / Three.js),仅依靠浏览器标准 WebGL2 API、Canvas 和原生 TypeScript 实现,构建后核心 bundle 体积小(单页 gzip 仅 1~15 KB),加载体验极致。
  2. 极高的类型安全度(0 any)
    • 在开启严格模式(strict: true, exactOptionalPropertyTypes: true, noImplicitOverride: true, noFallthroughCasesInSwitch: true)的前提下,src/ 源码目录中实现了 0 个 as any,0 个 : any。
    • 大量采用 readonly 冻结接口字段,从类型系统层面天然保障了状态不可变性,杜绝了多模块间就地修改(In-place mutation)导致的潜在 desync。
  3. 极具创造性的离线资源烘焙管线(Offline Asset Packing)
    • 通过 scripts/pack-act-assets.ts 将原本在客户端需要发起的 11,000+ 次 MPQ HTTP Range 分片请求与复杂解码,预先烘焙为 2048² 调色板索引 PNG 图集与紧凑的 scene.json,把 Act 1 的网络请求数从 1,344 次骤降至 4 次,首屏时间从 12s 缩短至 1.5s。
  4. 科学严谨的差分验证方法论
    • 广泛与外部独立实现(OpenDiablo2、D2MOO、StormLib、DevilutionX)进行跨语言差分对齐,并编写了覆盖 M0~M5 各里程碑的高密度断言脚本。

二、核心架构缺陷与高危质量问题

1. 【高危】tbl.ts 格式假设偏差严重(标准 D2 实际为哈希表结构而非线性索引)

  • 代码位置:src/formats/tbl.ts
  • 问题分析:
    • tbl.ts 实现的是简单的 [CRC][Count][Offsets...][UTF-16LE Strings] 线性顺序布局。
    • 作者在注释中自述“以为 Go 侧带哈希表的实现是后来扩展格式”,因此只做了自闭环的合成测试(lib/tbl-writer.ts)。
    • 事实:Diablo II 原版所有的 .tbl 文件(string.tbl, expansionstring.tbl, patchstring.tbl)均是基于哈希桶(Hash Table)、元素头(TBLHeader)与以 null 结尾的 ANSI/Code-Page 字符串键值结构。
  • 影响:
    • 直接导致官方 CHI/string.tbl 解码出全是乱码;
    • 迫使项目退化为用 level-names-zh.ts 硬编码中文场景名;
    • 阻碍了 Issue #6 中所有 NPC 名字、对话台词、物品/技能/任务多语言文本的原版动态加载。

2. 【高危】SpriteRenderer 缺少显存资源释放(WebGL2 显存持续泄露)

  • 代码位置:src/render/renderer.ts,src/scene/act-scene.ts
  • 问题分析:
    • SpriteRenderer 在构造函数中分配了 Program、VertexShader、FragmentShader、VAO、VBO 以及白底/图集纹理;
    • addAtlas 每次加载关卡切片均调用 gl.createTexture() 创建 2048×2048 的 GPU 纹理;
    • 全工程没有任何一处调用 gl.deleteTexture、gl.deleteProgram、gl.deleteBuffer 或 gl.deleteVertexArray,SpriteRenderer 甚至没有提供 destroy() 或 dispose() 接口。
  • 影响:
    • 每次切换关卡、切场景或重新加载时,前序纹理占用完全不被 GPU 回收,低端设备或移动端长时间运行极易遭遇 WebGL Context Lost。

3. 【架构】场景系统严重割裂(map-scene 沙盒与 act-scene 漫游器断层)

  • 代码位置:src/scene/map-scene.ts vs src/scene/act-scene.ts
  • 问题分析:
    • map-scene.ts(约 1200 行)拥有完整的战斗、怪物 AI、攻击/施法、掉落拾取、背包、技能槽、NPC 交谈与存档读档;但它只能在夹具小地图中运行。
    • act-scene.ts(约 1125 行)接入了真实的 5 个 Act、35 个正式关卡预解包数据和女法师 DCC/COF,但它仅仅是个漫游器,没有任何战斗、怪物、NPC、背包和交互系统。
    • 两个场景各自手写了一整套相机计算、键盘监听、HUD 文本拼接与状态暴露(window.__d2web*)。
  • 影响:
    • 核心领域模型(World、Entity、Combat)没有与视图层(Scene)解耦,导致在真实地图中无法体验已经写好的完整游戏玩法。

4. 【鲁棒性】底层二进制解码过于依赖非空断言 !(缺失越界防御)

  • 代码位置:src/formats/bitstream.ts、pcx.ts、cel.ts、cof.ts、dcc.ts、dt1.ts 等
  • 问题分析:
    • 在解包解析二进制流时,超过 500+ 处密集使用 data[i]! 压制 TypeScript 的空安全检查。
    • 若输入数据存在损坏、截断或越界,不会触发自定义的领域异常(如 CelError / MpqError),而是直接抛出运行时的 TypeError: Cannot read properties of undefined。
  • 影响:
    • 错误栈脱离了项目自定义的异常体系,无法定位到具体损坏的 block 或 sector,弱化了防御性编程的边界保护。

5. 【工程】测试与工具链缺乏跨平台可移植性

  • 代码位置:package.json
  • 问题分析:
    • package.json 的所有测试命令(verify:*)直接写为 node scripts/*.ts。
    • 这硬编码假定了环境使用的是官方官方预编译、开启了实验性 amaro 剥离的 Node.js 运行时。
    • 在使用 Linux 发行版官方包管理器安装的标准 Node.js 环境(如 Ubuntu/Debian 官方 apt 源构建)中,直接崩溃报 ERR_UNKNOWN_FILE_EXTENSION 或 ERR_NO_TYPESCRIPT: Node.js is not compiled with TypeScript support。
  • 影响:
    • 新开发者克隆项目后运行 npm run verify:all 无法开箱即用,CI 流水线亦易受 Node 运行时环境差异影响。

6. 【功能完整度】生成关卡(101 关)与音频系统长期搁置

  • 代码位置:src/game/maze.ts、src/game/wilderness.ts、src/mpq/decompress.ts
  • 问题分析:
    • maze.ts(1796 行)与 wilderness.ts(909 行)实现了庞大的随机迷宫与野外生成器,但因为碎片引用缺失与拼接可达率低,在 pack-act-assets.ts 中被全部跳过,导致网页端缺少 101 个真实关卡(包含鲜血荒野等标志性区域)。
    • MPQ 中的 ADPCM + Huffman 音频解码(约 572 个音频成员)尚未实现,引擎目前完全处于无声状态。

三、技术债务与代码异味清单

类别 文件 / 位置 异味描述 建议重构方案
类型断言 src/game/character.ts:195 const sprite = entry.sprite as DccFile 强制转型 使用显式类型收窄或 Type Guard 函数校验
类型断言 src/game/save.ts:174 snapshot.inventory.placed as PlacedItem[] 强转 增加快照数据 schema 验证逻辑
硬编码路径 src/game/objects.ts:76 OBJECT_ROOT = 'data\\global\\objects\\' 硬编码 提取基址选择器,根据 :m 标记自适应切换 monsters
测试架构 scripts/verify-*.ts 散落的独立脚本,手写 console.log 与退出码 迁移至现代化测试框架(如 Vitest),支持 watch 与覆盖率报告
联机边界 src/net/lockstep.ts 物品拾取、背包与任务进度未纳入锁步状态哈希计算 将背包与任务状态加入 StateHash,达成完全确定性同步

四、演进重构建议路线图

  • Phase 1(可移植性与基础设施修复):
    • 在 devDependencies 中引入 tsx,将 package.json 中的 node scripts/*.ts 统一调整为 tsx scripts/*.ts,保证任何 Node 环境均能稳定跑通全部 verify 脚本;
    • 接入 Vitest,将 verify-combat、verify-items、verify-net 等迁移为标准测试用例。
  • Phase 2(核心格式修复):
    • 重构 src/formats/tbl.ts:依照 D2 标准哈希桶布局重写解码器,打通 string.tbl 真实数据源,淘汰 level-names-zh.ts 临时静态表。
  • Phase 3(资源管理与渲染健壮性):
    • 为 SpriteRenderer 实现 dispose() 方法,在场景切换与销毁时显式调用 gl.deleteTexture 等回收 GPU 显存;
    • 在二进制读取处对 subarray 与索引做安全边界校验,替换裸 ! 断言。
  • Phase 4(场景融合与玩法落地):
    • 抽离出 GameWorld 领域核心层,把 map-scene.ts 的战斗、物品、NPC、UI 与 acts.html 的真实 5-Act 大地图融为一体。
## 项目全景代码质量与架构审查报告 经过对 `troytt/diablo2-web` 仓库全模块(MPQ 容器、各二进制格式解码器、游戏模拟子系统、WebGL2 渲染器、锁步网络层、场景与打包管线)的深度静态分析、逻辑审计与验证套件执行,现将项目的**整体质量评价**、**核心缺陷与高危风险**、**技术债务**及**演进重构建议**汇总如下。 --- ### 一、整体质量与工程亮点 1. **零运行时外部依赖(Pure TS + WebGL2)** - 运行时完全摆脱重量级游戏引擎(如 Pixi.js / Phaser / Three.js),仅依靠浏览器标准 WebGL2 API、Canvas 和原生 TypeScript 实现,构建后核心 bundle 体积小(单页 gzip 仅 1~15 KB),加载体验极致。 2. **极高的类型安全度(0 any)** - 在开启严格模式(`strict: true`, `exactOptionalPropertyTypes: true`, `noImplicitOverride: true`, `noFallthroughCasesInSwitch: true`)的前提下,`src/` 源码目录中实现了 **0 个 `as any`,0 个 `: any`**。 - 大量采用 `readonly` 冻结接口字段,从类型系统层面天然保障了状态不可变性,杜绝了多模块间就地修改(In-place mutation)导致的潜在 desync。 3. **极具创造性的离线资源烘焙管线(Offline Asset Packing)** - 通过 `scripts/pack-act-assets.ts` 将原本在客户端需要发起的 11,000+ 次 MPQ HTTP Range 分片请求与复杂解码,预先烘焙为 2048² 调色板索引 PNG 图集与紧凑的 `scene.json`,把 Act 1 的网络请求数从 1,344 次骤降至 4 次,首屏时间从 12s 缩短至 1.5s。 4. **科学严谨的差分验证方法论** - 广泛与外部独立实现(OpenDiablo2、D2MOO、StormLib、DevilutionX)进行跨语言差分对齐,并编写了覆盖 M0~M5 各里程碑的高密度断言脚本。 --- ### 二、核心架构缺陷与高危质量问题 #### 1. 【高危】`tbl.ts` 格式假设偏差严重(标准 D2 实际为哈希表结构而非线性索引) - **代码位置**:`src/formats/tbl.ts` - **问题分析**: - `tbl.ts` 实现的是简单的 `[CRC][Count][Offsets...][UTF-16LE Strings]` 线性顺序布局。 - 作者在注释中自述“以为 Go 侧带哈希表的实现是后来扩展格式”,因此只做了自闭环的合成测试(`lib/tbl-writer.ts`)。 - **事实**:Diablo II 原版所有的 `.tbl` 文件(`string.tbl`, `expansionstring.tbl`, `patchstring.tbl`)均是基于哈希桶(Hash Table)、元素头(`TBLHeader`)与以 null 结尾的 ANSI/Code-Page 字符串键值结构。 - **影响**: - 直接导致官方 `CHI/string.tbl` 解码出全是乱码; - 迫使项目退化为用 `level-names-zh.ts` 硬编码中文场景名; - 阻碍了 Issue #6 中所有 NPC 名字、对话台词、物品/技能/任务多语言文本的原版动态加载。 #### 2. 【高危】`SpriteRenderer` 缺少显存资源释放(WebGL2 显存持续泄露) - **代码位置**:`src/render/renderer.ts`,`src/scene/act-scene.ts` - **问题分析**: - `SpriteRenderer` 在构造函数中分配了 Program、VertexShader、FragmentShader、VAO、VBO 以及白底/图集纹理; - `addAtlas` 每次加载关卡切片均调用 `gl.createTexture()` 创建 2048×2048 的 GPU 纹理; - 全工程**没有任何一处**调用 `gl.deleteTexture`、`gl.deleteProgram`、`gl.deleteBuffer` 或 `gl.deleteVertexArray`,`SpriteRenderer` 甚至没有提供 `destroy()` 或 `dispose()` 接口。 - **影响**: - 每次切换关卡、切场景或重新加载时,前序纹理占用完全不被 GPU 回收,低端设备或移动端长时间运行极易遭遇 `WebGL Context Lost`。 #### 3. 【架构】场景系统严重割裂(`map-scene` 沙盒与 `act-scene` 漫游器断层) - **代码位置**:`src/scene/map-scene.ts` vs `src/scene/act-scene.ts` - **问题分析**: - `map-scene.ts`(约 1200 行)拥有完整的战斗、怪物 AI、攻击/施法、掉落拾取、背包、技能槽、NPC 交谈与存档读档;但它**只能在夹具小地图中运行**。 - `act-scene.ts`(约 1125 行)接入了真实的 5 个 Act、35 个正式关卡预解包数据和女法师 DCC/COF,但它**仅仅是个漫游器**,没有任何战斗、怪物、NPC、背包和交互系统。 - 两个场景各自手写了一整套相机计算、键盘监听、HUD 文本拼接与状态暴露(`window.__d2web*`)。 - **影响**: - 核心领域模型(`World`、`Entity`、`Combat`)没有与视图层(Scene)解耦,导致在真实地图中无法体验已经写好的完整游戏玩法。 #### 4. 【鲁棒性】底层二进制解码过于依赖非空断言 `!`(缺失越界防御) - **代码位置**:`src/formats/bitstream.ts`、`pcx.ts`、`cel.ts`、`cof.ts`、`dcc.ts`、`dt1.ts` 等 - **问题分析**: - 在解包解析二进制流时,超过 500+ 处密集使用 `data[i]!` 压制 TypeScript 的空安全检查。 - 若输入数据存在损坏、截断或越界,不会触发自定义的领域异常(如 `CelError` / `MpqError`),而是直接抛出运行时的 `TypeError: Cannot read properties of undefined`。 - **影响**: - 错误栈脱离了项目自定义的异常体系,无法定位到具体损坏的 block 或 sector,弱化了防御性编程的边界保护。 #### 5. 【工程】测试与工具链缺乏跨平台可移植性 - **代码位置**:`package.json` - **问题分析**: - `package.json` 的所有测试命令(`verify:*`)直接写为 `node scripts/*.ts`。 - 这硬编码假定了环境使用的是官方官方预编译、开启了实验性 `amaro` 剥离的 Node.js 运行时。 - 在使用 Linux 发行版官方包管理器安装的标准 Node.js 环境(如 Ubuntu/Debian 官方 apt 源构建)中,直接崩溃报 `ERR_UNKNOWN_FILE_EXTENSION` 或 `ERR_NO_TYPESCRIPT: Node.js is not compiled with TypeScript support`。 - **影响**: - 新开发者克隆项目后运行 `npm run verify:all` 无法开箱即用,CI 流水线亦易受 Node 运行时环境差异影响。 #### 6. 【功能完整度】生成关卡(101 关)与音频系统长期搁置 - **代码位置**:`src/game/maze.ts`、`src/game/wilderness.ts`、`src/mpq/decompress.ts` - **问题分析**: - `maze.ts`(1796 行)与 `wilderness.ts`(909 行)实现了庞大的随机迷宫与野外生成器,但因为碎片引用缺失与拼接可达率低,在 `pack-act-assets.ts` 中被全部跳过,导致网页端缺少 101 个真实关卡(包含鲜血荒野等标志性区域)。 - MPQ 中的 ADPCM + Huffman 音频解码(约 572 个音频成员)尚未实现,引擎目前完全处于无声状态。 --- ### 三、技术债务与代码异味清单 | 类别 | 文件 / 位置 | 异味描述 | 建议重构方案 | | :--- | :--- | :--- | :--- | | **类型断言** | `src/game/character.ts:195` | `const sprite = entry.sprite as DccFile` 强制转型 | 使用显式类型收窄或 Type Guard 函数校验 | | **类型断言** | `src/game/save.ts:174` | `snapshot.inventory.placed as PlacedItem[]` 强转 | 增加快照数据 schema 验证逻辑 | | **硬编码路径** | `src/game/objects.ts:76` | `OBJECT_ROOT = 'data\\global\\objects\\'` 硬编码 | 提取基址选择器,根据 `:m` 标记自适应切换 `monsters` | | **测试架构** | `scripts/verify-*.ts` | 散落的独立脚本,手写 `console.log` 与退出码 | 迁移至现代化测试框架(如 Vitest),支持 watch 与覆盖率报告 | | **联机边界** | `src/net/lockstep.ts` | 物品拾取、背包与任务进度未纳入锁步状态哈希计算 | 将背包与任务状态加入 `StateHash`,达成完全确定性同步 | --- ### 四、演进重构建议路线图 - **Phase 1(可移植性与基础设施修复)**: - 在 `devDependencies` 中引入 `tsx`,将 `package.json` 中的 `node scripts/*.ts` 统一调整为 `tsx scripts/*.ts`,保证任何 Node 环境均能稳定跑通全部 verify 脚本; - 接入 Vitest,将 `verify-combat`、`verify-items`、`verify-net` 等迁移为标准测试用例。 - **Phase 2(核心格式修复)**: - 重构 `src/formats/tbl.ts`:依照 D2 标准哈希桶布局重写解码器,打通 `string.tbl` 真实数据源,淘汰 `level-names-zh.ts` 临时静态表。 - **Phase 3(资源管理与渲染健壮性)**: - 为 `SpriteRenderer` 实现 `dispose()` 方法,在场景切换与销毁时显式调用 `gl.deleteTexture` 等回收 GPU 显存; - 在二进制读取处对 `subarray` 与索引做安全边界校验,替换裸 `!` 断言。 - **Phase 4(场景融合与玩法落地)**: - 抽离出 `GameWorld` 领域核心层,把 `map-scene.ts` 的战斗、物品、NPC、UI 与 `acts.html` 的真实 5-Act 大地图融为一体。
Author
Owner

关联子 Issue 拆分清单 (Sub-Issues Breakdown)

  • #8 [Bug] tbl.ts 字符串表解码器偏差:线性假想索引未对齐暗黑2官方哈希桶(Hash Table)键值格式
  • #9 [显存/性能] SpriteRenderer 缺少显存资源释放:WebGL2 纹理、缓冲与程序持续泄漏 (Fixed in a80ee2c)
  • #10 [架构重构] 场景系统架构割裂:抽离统一游戏世界领域层,消除 map-scene 沙盒与 act-scene 大地图割裂
  • #11 [代码质量/鲁棒性] 底层二进制解码过度依赖非空断言 !:缺失防御性越界校验与异常统一
  • #12 [工程化/环境] 测试与构建脚本执行强依赖特定 Node.js 运行时:引入 tsx 解决跨平台与 CI 无法运行 TypeScript 脚本
  • #13 [音频/多媒体] MPQ ADPCM + Huffman 音频解码缺失:支持 572 个音频成员并接入 Web Audio API
  • #14 [测试工程] 缺少现代化单元测试框架:引入 Vitest 规范化测试套件、覆盖率统计与持续集成
### 关联子 Issue 拆分清单 (Sub-Issues Breakdown) - [ ] #8 [Bug] tbl.ts 字符串表解码器偏差:线性假想索引未对齐暗黑2官方哈希桶(Hash Table)键值格式 - [x] #9 [显存/性能] SpriteRenderer 缺少显存资源释放:WebGL2 纹理、缓冲与程序持续泄漏 (Fixed in a80ee2c) - [ ] #10 [架构重构] 场景系统架构割裂:抽离统一游戏世界领域层,消除 map-scene 沙盒与 act-scene 大地图割裂 - [ ] #11 [代码质量/鲁棒性] 底层二进制解码过度依赖非空断言 !:缺失防御性越界校验与异常统一 - [ ] #12 [工程化/环境] 测试与构建脚本执行强依赖特定 Node.js 运行时:引入 tsx 解决跨平台与 CI 无法运行 TypeScript 脚本 - [ ] #13 [音频/多媒体] MPQ ADPCM + Huffman 音频解码缺失:支持 572 个音频成员并接入 Web Audio API - [ ] #14 [测试工程] 缺少现代化单元测试框架:引入 Vitest 规范化测试套件、覆盖率统计与持续集成
Author
Owner

已处理 — 代码审查母议题

本议题点出的三个其他议题未覆盖的实质缺陷已全部修复(提交以 refs #7 标注):

  1. 锁步 StateHash 遗漏背包 / 地面掉落 / 任务进度 —— 这是最严重的一个:两个 peer 可以在「谁捡到了什么」上产生分歧,而 desync 检测器却报告一致。现已实现 computeLockstepHash(src/net/lockstep.ts:263),用 FNV-1a 折叠,地面物品用顺序无关求和。
    • 我方独立校验过顺序无关求和是否存在可加性碰撞:swapped positions differ: PASS (1152271537 vs 3289542833)、reordered array equal: PASS。成立的原因是每个元素在求和前先经过乘法型 FNV-1a 折叠。
    • 注意:初版实现是死代码 —— 真正的线上路径是 net-scene.ts 里的 digestWorld()。该边界打开后已正确接线,digestWorld() 已移除。变异测试(使 mix(invHash) 失效)可同时触发单测与集成测试。
  2. 两处不安全的 as 转换,其中一处作用在反序列化的存档数据上 —— src/game/save.ts 已改为带 asserts value is PlacedItem 的结构化校验;src/game/character.ts 的 as DccFile 改为 flatMap + 类型守卫。
  3. 顺带清理了 9 处 any 与 3 处每 tick 的重复分配。

另外发现并修复了一个无人报告的缺口: scripts/verify-tbl.ts 早已存在且可用,但 package.json 里根本没有 verify:tbl 这个 key,等于从未被执行。现已补上,并把 verify:tbl、verify:audio、verify:formats 一并接入 verify:all。


两点需要如实说明(不静默略过):

(A) SpriteRenderer 的 VRAM 泄漏指控已过时。 该问题早在 a80ee2c 中随已关闭的 #9 修复。我最初误判它只在 beforeunload 时释放,进一步追查后撤回该判断:幕选择器走的是 location.search 赋值(整页导航),deleteAtlas 覆盖了角色句柄与两个页面数组(act-scene.ts:960-966),且 packBase 循环在每条成功路径上都 return,因此不会堆叠 renderer。此处无需改动。

(B) 「101 个生成关卡」(maze.ts / wilderness.ts) 本次明确不在范围内。 理由:它只出现在本议题的观察部分,并未进入本议题自己给出的四阶段整改路线图(该路线图恰好对应 #12/#14 → #8 → #9/#11 → #10);且本仓库缺少 samples/d2,pack-act-assets.ts 根本无法运行,无从验证。建议另开议题跟踪。


当前 main 的整体状态(冷装 npm ci 后实测):

检查项 修复前基线 现在
tsc --noEmit 10 errors 0
vitest run 22 failed 396 passed / 27 skipped / 0 failed
--sequence.shuffle — 稳定
grep ": any|as any" src/ 多处 干净
tests/ 中的 process.exit 5 个文件 无
### 已处理 — 代码审查母议题 本议题点出的三个**其他议题未覆盖**的实质缺陷已全部修复(提交以 `refs #7` 标注): 1. **锁步 `StateHash` 遗漏背包 / 地面掉落 / 任务进度** —— 这是最严重的一个:两个 peer 可以在「谁捡到了什么」上产生分歧,而 desync 检测器却报告一致。现已实现 `computeLockstepHash`(`src/net/lockstep.ts:263`),用 FNV-1a 折叠,地面物品用**顺序无关**求和。 - 我方独立校验过顺序无关求和是否存在可加性碰撞:`swapped positions differ: PASS (1152271537 vs 3289542833)`、`reordered array equal: PASS`。成立的原因是每个元素在求和前先经过乘法型 FNV-1a 折叠。 - 注意:初版实现是**死代码** —— 真正的线上路径是 `net-scene.ts` 里的 `digestWorld()`。该边界打开后已正确接线,`digestWorld()` 已移除。变异测试(使 `mix(invHash)` 失效)可同时触发单测与集成测试。 2. **两处不安全的 `as` 转换**,其中一处作用在反序列化的存档数据上 —— `src/game/save.ts` 已改为带 `asserts value is PlacedItem` 的结构化校验;`src/game/character.ts` 的 `as DccFile` 改为 `flatMap` + 类型守卫。 3. 顺带清理了 9 处 `any` 与 3 处每 tick 的重复分配。 **另外发现并修复了一个无人报告的缺口:** `scripts/verify-tbl.ts` 早已存在且可用,但 `package.json` 里根本没有 `verify:tbl` 这个 key,等于从未被执行。现已补上,并把 `verify:tbl`、`verify:audio`、`verify:formats` 一并接入 `verify:all`。 --- **两点需要如实说明(不静默略过):** **(A) `SpriteRenderer` 的 VRAM 泄漏指控已过时。** 该问题早在 `a80ee2c` 中随已关闭的 #9 修复。我最初误判它只在 `beforeunload` 时释放,进一步追查后**撤回该判断**:幕选择器走的是 `location.search` 赋值(整页导航),`deleteAtlas` 覆盖了角色句柄与两个页面数组(`act-scene.ts:960-966`),且 `packBase` 循环在每条成功路径上都 return,因此不会堆叠 renderer。此处无需改动。 **(B) 「101 个生成关卡」(`maze.ts` / `wilderness.ts`) 本次明确不在范围内。** 理由:它只出现在本议题的观察部分,并未进入本议题自己给出的四阶段整改路线图(该路线图恰好对应 #12/#14 → #8 → #9/#11 → #10);且本仓库缺少 `samples/d2`,`pack-act-assets.ts` 根本无法运行,无从验证。建议另开议题跟踪。 --- **当前 `main` 的整体状态(冷装 `npm ci` 后实测):** | 检查项 | 修复前基线 | 现在 | | :-- | :-- | :-- | | `tsc --noEmit` | 10 errors | **0** | | `vitest run` | 22 failed | **396 passed / 27 skipped / 0 failed** | | `--sequence.shuffle` | — | 稳定 | | `grep ": any\|as any" src/` | 多处 | 干净 | | `tests/` 中的 `process.exit` | 5 个文件 | 无 |
Sign in to join this conversation.
No description provided.