chore: 消除重复真相源、死代码与形同虚设的逻辑规则 #63

Closed
opened 2026-09-17 11:12:42 +00:00 by troytt · 2 comments
Owner

背景

对 main 分支(origin/main = 4c2e399)的地图与怪物生成层做全量审计时,附带发现了一批重复真相源与死代码 / 未接线问题。

这些问题单独看都不致命,但它们是「两份数据静默分歧」类 bug 的温床,且会误导后续维护者。


1. 重复真相源(Duplicate Sources of Truth)

1a. 城镇关卡 ID 列表存在三份

位置 形式 备注
src/game/portal.ts:226-238 townLevelForAct switch,含 default: 109 幕号异常时静默落到第五幕
src/game/acts.ts:312 按名字匹配 Act N - Town 唯一数据驱动的一份
scripts/pack-act-assets.ts:369-370 ACT_TOWNS = [1, 40, 75, 103, 109] —

1b. 关卡 → 幕映射存在两份且边界不一致

  • src/game/wilderness.ts:3686-3698 — actOfLevel 用 >= 1 && <= 39
  • src/game/wilderness.ts:4937-4945 — inferredAct 用 < 40

且同一套 id 区间派发在 wilderness.ts:3589-3666 又重复了第三遍。

1c. 血腥荒地(关卡 2)预设列表存在两份且分歧

  • src/game/wilderness.ts:2474 — SPECIAL_PRESETS_BY_LEVEL[2]
  • src/game/wilderness.ts:5163-5199 — 内联的 50% 河流变体清单

1d. PACK_RADIUS_PX 两个不兼容的值

  • src/game/monster-rooms.ts:66 = 64
  • src/game/combat.ts:463 = 96

同一概念,取哪个取决于走哪条刷怪路径。

1e. SUB_TILES_PER_TILE 定义了 4 次

src/game/map.ts(规范)、src/game/maze-objects.ts:24、src/game/level-links.ts:39 等。应统一 import。

另有 wilderness-acts.ts:296-377 直接用字面 * 5 而非引用常量。

1f. readSuperUniques() / readEliteModifiers() 与手写表并存

src/game/monsters.ts:605-644 / :660-682 解析真表,:1002-1345 / :687-707 是手写表。两个真相源,解析器从未被调用。
(详见「用 MPQ 表取代手写怪物数据」Issue)


2. 死代码(Dead Code)

2a. generateAct3Jungle —— import 了但从未调用

src/game/wilderness-acts.ts:417-528(约 110 行)

在 src/game/wilderness.ts:85 被 import,没有任何调用点。

它的硬编码坐标(bridgeYPositions = [78, 142]、桥跨度 x 32…60)还被 wilderness-jungles.ts:590-609 复制了一份。

2b. maze-objects.ts:238 的 || true

const hasFloor = cell.floors.length > 0 && cell.floors.some(f => !f.hidden && (f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0 || true))

prop1 / style / sequence 内容检测恒为真,整段是死的。
(同时也是一个真 bug,另见「消除静默降级」Issue)

2c. readMonsterArt 已解析 MeleeRng 后丢弃

src/game/monsters.ts:376-389 把 MonStats2.MeleeRng 解析进 MonsterArt.meleeRange,然后 :965-975 用常量 DEFAULT_REACH_PX = 40。

同理 MonsterKind.code(:171-172)被解析后由 741 行的 MONSTER_ART_MAP 取代。

2d. LevelRow.offsetX / offsetY 解析后从未读回

src/game/world-graph.ts:399-400 解析了这两列,文件头部 :20-24 还明确指出 OffsetX/OffsetY = -1 恰好标记被缝合的关卡 —— 然后用 46 条手打的 SEAMLESS_ADJACENCY 代替。

2e. MonUMod.txt 的 exclude1 / exclude2 解析后忽略

src/game/monsters.ts:666-682 解析互斥规则,:712-730 的 pool.splice 完全不用。

2f. 13 个精英词缀里 11 个无任何机制效���

src/game/monsters.ts:735-757:只有 strong / fast 生效;cursed / fireenchant / coldenchant / lightenchant / stoneskin 等只被存储。


3. 未接线(Wired but Never Reached)

3a. difficulty 参数从未传递

src/scene/act-scene.ts:906-912 与 scripts/pack-act-assets.ts:1372 调用 planLevelMonsters 时都不传 difficulty,恒为 'normal'。

后果:nmon* 池、(N)/(H) 列、NM/Hell 缩放、monsters.ts:479-483 的区域等级规则全是死代码。

3b. monster-rooms.ts 整套房间系统在生产路径被绕过

src/scene/act-scene.ts:1589 调用 spawnMonsterPacks 时不传 rooms,导致 544 行的房间放置系统完全不生效。

而 monster-rooms.ts:518-531 的向后兼容分支会凭空捏造一个 64×64 的房间网格。

3c. 怪物只合成 wl + nu 两种动画

src/game/monster-art.ts:250-260:攻击 / 受击 / 死亡 / 特殊动画的 COF 与 DCC 都在磁盘上,但从不被合成。

3d. planMonsterGroups 忽略 Rarity

src/game/monsters.ts:884-889 用 types[rng.int(0, len-1)] 均匀随机选型。Rarity 只在 selectLevelTypes 用过一次。

3e. 只读 mon1..mon10

src/game/monsters.ts:536-543 的 for (let slot = 1; slot <= 10; …),而 Levels.txt 支持 mon1..mon25。

未改版 1.13c 无影响,任何 mod 会被静默截断。


4. 逻辑恒真 / 恒假(形同虚设的规则)

位置 问题
wilderness.ts:2443-2454 BordType 0 的「整关仅一个」上限因 min(1, Infinity) 恒等于 1,实际是「每行一个」,预算从未起约束作用
maze-objects.ts:604-606 架子候选房间条件 orths.length <= 2 实际等于「所有房间」,「仅走廊」规则形同虚设
maze-special-passes.ts:549-563 nDirection = seed % 3 后按 (nDirection+1) % 4 索引 ⇒ 索引恒 ∈ {1,2,3},'NE' 路径点永远不可达
monster-rooms.ts:276-280 if (candidateRooms.length === 0) candidateRooms = rooms.slice() ⇒ 所有房间都是安全区时,安全区规则被整体丢弃,怪物可刷进城镇
wilderness.ts:2240-2243 isRoad(nx, ny) 越界时返回 true ⇒ 地图外被当作道路,整个外圈的道路瓦片 LUT 索引错误

5. 其他小问题

  • monster-rooms.ts:113-121 / :155-157:cellPixel* ?? 80 两个轴都默认 80,但本引擎的等距格是 80×40 —— 高度默认值是错的。
  • monster-rooms.ts:282:new Rng(seed ?? 0x5eed) ⇒ 所有未传种子的关卡共用同一条 RNG 流,布局完全相同。
  • wilderness.ts:5049:特殊路径恒报 subTheme: 0。
  • acts.ts:76:parseTable 的 lines.shift() ?? '' ⇒ 零字节 Levels.txt 会产出一个「空但有效」的世界,而不是报错。
  • acts.ts:372-374:为 Act 5 - Barricade 注入的 Expansion/Siege/temptile.dt1 不在 LvlTypes.File 1..32 中,来源不明,需补 provenance 注释。

验收标准

  • 城镇 ID 只剩一份真相源(acts.ts 的数据驱动版本)
  • 关卡→幕映射只剩一份实现,改读 Levels.txt Act
  • 血腥荒地预设列表合并为一份
  • PACK_RADIUS_PX 统一为单一常量
  • SUB_TILES_PER_TILE 只保留 map.ts 一份,其余改为 import
  • generateAct3Jungle 删除
  • maze-objects.ts:238 的 || true 删除
  • difficulty 参数贯通;或显式记录「暂不支持 NM/Hell」并加断言
  • monster-rooms.ts 房间系统接入生产路径,或明确删除
  • 第 4 节列出的 5 处恒真/恒假逻辑全部修复,每处附回归测试
  • npm run typecheck 零错误,npx vitest run 全绿

TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1

## 背景 对 `main` 分支(`origin/main` = `4c2e399`)的地图与怪物生成层做全量审计时,附带发现了一批**重复真相源**与**死代码 / 未接线**问题。 这些问题单独看都不致命,但它们是「两份数据静默分歧」类 bug 的温床,且会误导后续维护者。 --- ## 1. 重复真相源(Duplicate Sources of Truth) ### 1a. 城镇关卡 ID 列表存在**三份** | 位置 | 形式 | 备注 | | :--- | :--- | :--- | | `src/game/portal.ts:226-238` | `townLevelForAct` switch,含 `default: 109` | 幕号异常时静默落到第五幕 | | `src/game/acts.ts:312` | 按名字匹配 `Act N - Town` | **唯一数据驱动的一份** | | `scripts/pack-act-assets.ts:369-370` | `ACT_TOWNS = [1, 40, 75, 103, 109]` | — | ### 1b. 关卡 → 幕映射存在**两份且边界不一致** - `src/game/wilderness.ts:3686-3698` — `actOfLevel` 用 `>= 1 && <= 39` - `src/game/wilderness.ts:4937-4945` — `inferredAct` 用 `< 40` 且同一套 id 区间派发在 `wilderness.ts:3589-3666` 又重复了第三遍。 ### 1c. 血腥荒地(关卡 2)预设列表存在**两份且分歧** - `src/game/wilderness.ts:2474` — `SPECIAL_PRESETS_BY_LEVEL[2]` - `src/game/wilderness.ts:5163-5199` — 内联的 50% 河流变体清单 ### 1d. `PACK_RADIUS_PX` 两个**不兼容**的值 - `src/game/monster-rooms.ts:66` = **64** - `src/game/combat.ts:463` = **96** 同一概念,取哪个取决于走哪条刷怪路径。 ### 1e. `SUB_TILES_PER_TILE` 定义了 **4 次** `src/game/map.ts`(规范)、`src/game/maze-objects.ts:24`、`src/game/level-links.ts:39` 等。应统一 import。 另有 `wilderness-acts.ts:296-377` 直接用字面 `* 5` 而非引用常量。 ### 1f. `readSuperUniques()` / `readEliteModifiers()` 与手写表并存 `src/game/monsters.ts:605-644` / `:660-682` 解析真表,`:1002-1345` / `:687-707` 是手写表。**两个真相源,解析器从未被调用。** (详见「用 MPQ 表取代手写怪物数据」Issue) --- ## 2. 死代码(Dead Code) ### 2a. `generateAct3Jungle` —— import 了但从未调用 `src/game/wilderness-acts.ts:417-528`(约 110 行) 在 `src/game/wilderness.ts:85` 被 import,**没有任何调用点**。 它的硬编码坐标(`bridgeYPositions = [78, 142]`、桥跨度 `x 32…60`)还被 `wilderness-jungles.ts:590-609` 复制了一份。 ### 2b. `maze-objects.ts:238` 的 `|| true` ```ts const hasFloor = cell.floors.length > 0 && cell.floors.some(f => !f.hidden && (f.prop1 !== 0 || f.style !== 0 || f.sequence !== 0 || true)) ``` `prop1 / style / sequence` 内容检测恒为真,整段是死的。 (同时也是一个真 bug,另见「消除静默降级」Issue) ### 2c. `readMonsterArt` 已解析 `MeleeRng` 后丢弃 `src/game/monsters.ts:376-389` 把 `MonStats2.MeleeRng` 解析进 `MonsterArt.meleeRange`,然后 `:965-975` 用常量 `DEFAULT_REACH_PX = 40`。 同理 `MonsterKind.code`(`:171-172`)被解析后由 741 行的 `MONSTER_ART_MAP` 取代。 ### 2d. `LevelRow.offsetX` / `offsetY` 解析后从未读回 `src/game/world-graph.ts:399-400` 解析了这两列,文件头部 `:20-24` 还明确指出 `OffsetX/OffsetY = -1` 恰好标记被缝合的关卡 —— 然后用 46 条手打的 `SEAMLESS_ADJACENCY` 代替。 ### 2e. `MonUMod.txt` 的 `exclude1` / `exclude2` 解析后忽略 `src/game/monsters.ts:666-682` 解析互斥规则,`:712-730` 的 `pool.splice` 完全不用。 ### 2f. 13 个精英词缀里 11 个无任何机制效��� `src/game/monsters.ts:735-757`:只有 `strong` / `fast` 生效;`cursed` / `fireenchant` / `coldenchant` / `lightenchant` / `stoneskin` 等只被存储。 --- ## 3. 未接线(Wired but Never Reached) ### 3a. `difficulty` 参数从未传递 `src/scene/act-scene.ts:906-912` 与 `scripts/pack-act-assets.ts:1372` 调用 `planLevelMonsters` 时**都不传 `difficulty`**,恒为 `'normal'`。 **后果**:`nmon*` 池、`(N)`/`(H)` 列、NM/Hell 缩放、`monsters.ts:479-483` 的区域等级规则全是死代码。 ### 3b. `monster-rooms.ts` 整套房间系统在生产路径被绕过 `src/scene/act-scene.ts:1589` 调用 `spawnMonsterPacks` 时**不传 rooms**,导致 544 行的房间放置系统完全不生效。 而 `monster-rooms.ts:518-531` 的向后兼容分支会**凭空捏造**一个 64×64 的房间网格。 ### 3c. 怪物只合成 `wl` + `nu` 两种动画 `src/game/monster-art.ts:250-260`:攻击 / 受击 / 死亡 / 特殊动画的 COF 与 DCC 都在磁盘上,但从不被合成。 ### 3d. `planMonsterGroups` 忽略 `Rarity` `src/game/monsters.ts:884-889` 用 `types[rng.int(0, len-1)]` 均匀随机选型。`Rarity` 只在 `selectLevelTypes` 用过一次。 ### 3e. 只读 `mon1..mon10` `src/game/monsters.ts:536-543` 的 `for (let slot = 1; slot <= 10; …)`,而 `Levels.txt` 支持 `mon1..mon25`。 未改版 1.13c 无影响,任何 mod 会被**静默截断**。 --- ## 4. 逻辑恒真 / 恒假(形同虚设的规则) | 位置 | 问题 | | :--- | :--- | | `wilderness.ts:2443-2454` | `BordType 0` 的「整关仅一个」上限因 `min(1, Infinity)` 恒等于 1,实际是「每行一个」,预算从未起约束作用 | | `maze-objects.ts:604-606` | 架子候选房间条件 `orths.length <= 2` 实际等于「所有房间」,「仅走廊」规则形同虚设 | | `maze-special-passes.ts:549-563` | `nDirection = seed % 3` 后按 `(nDirection+1) % 4` 索引 ⇒ 索引恒 ∈ {1,2,3},**`'NE'` 路径点永远不可达** | | `monster-rooms.ts:276-280` | `if (candidateRooms.length === 0) candidateRooms = rooms.slice()` ⇒ 所有房间都是安全区时,**安全区规则被整体丢弃**,怪物可刷进城镇 | | `wilderness.ts:2240-2243` | `isRoad(nx, ny)` 越界时返回 **`true`** ⇒ 地图外被当作道路,整个外圈的道路瓦片 LUT 索引错误 | --- ## 5. 其他小问题 - `monster-rooms.ts:113-121` / `:155-157`:`cellPixel* ?? 80` 两个轴都默认 80,但本引擎的等距格是 **80×40** —— 高度默认值是错的。 - `monster-rooms.ts:282`:`new Rng(seed ?? 0x5eed)` ⇒ 所有未传种子的关卡共用同一条 RNG 流,布局完全相同。 - `wilderness.ts:5049`:特殊路径恒报 `subTheme: 0`。 - `acts.ts:76`:`parseTable` 的 `lines.shift() ?? ''` ⇒ 零字节 `Levels.txt` 会产出一个「空但有效」的世界,而不是报错。 - `acts.ts:372-374`:为 `Act 5 - Barricade` 注入的 `Expansion/Siege/temptile.dt1` **不在 `LvlTypes.File 1..32` 中**,来源不明,需补 provenance 注释。 --- ## 验收标准 - [ ] 城镇 ID 只剩一份真相源(`acts.ts` 的数据驱动版本) - [ ] 关卡→幕映射只剩一份实现,改读 `Levels.txt Act` - [ ] 血腥荒地预设列表合并为一份 - [ ] `PACK_RADIUS_PX` 统一为单一常量 - [ ] `SUB_TILES_PER_TILE` 只保留 `map.ts` 一份,其余改为 import - [ ] `generateAct3Jungle` 删除 - [ ] `maze-objects.ts:238` 的 `|| true` 删除 - [ ] `difficulty` 参数贯通;或显式记录「暂不支持 NM/Hell」并加断言 - [ ] `monster-rooms.ts` 房间系统接入生产路径,或明确删除 - [ ] 第 4 节列出的 5 处恒真/恒假逻辑全部修复,每处附回归测试 - [ ] `npm run typecheck` 零错误,`npx vitest run` 全绿 TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
troytt added this to the [M15] 生成器数据保真度:消除 hardcode 与静默降级 milestone 2026-09-17 11:12:58 +00:00
Author
Owner

父追踪 Issue:#64(完整审计报告与修复路线图)

父追踪 Issue:#64(完整审计报告与修复路线图)
troytt referenced this issue from a commit 2026-09-17 16:05:58 +00:00
Author
Owner

✅ Issue #63 修复与验证完成 (Closes #63)

变更总结

  1. 修复 5 处逻辑恒真/恒假与参数缺陷:
    • wilderness.ts: isRoad(nx, ny) 越界检测修正为返回 false,消除地图边缘非道路格误用道路瓦片 LUT 的 Bug。
    • maze-objects.ts: 武器/防具架候选房间严格优先走廊房间(orths.length === 2 || isBarracks),消除导致走廊优先规则形同虚设的恒真条件。
    • monster-rooms.ts: 在 allocatePacksToRooms 中,当候选非安全区房间为空(例如城镇或纯安全区关卡)时直接返回,禁止将普通怪物刷入城镇/安全区。
    • monster-rooms.ts: 修正 partitionMapIntoRooms 中默认等距单元格高度为 40px(对齐 80×40 等距投影几何)。
    • acts.ts: parseTable 在传入 0 字节或全空表数据时显式抛出 Error('parseTable: empty table data');补充 Expansion/Siege/temptile.dt1 的来源注释(Blizzard LOD patch 拼装 Act 5 攻城 DS1 依赖的地面过渡瓦片)。
  2. 统一真相源并消除重复硬编码:
    • wilderness.ts: 提取 BLOOD_MOOR_RIVER_PRESETS 与 BLOOD_MOOR_POND_PRESETS,消除血腥荒地预设列表在两处的数组副本分歧。
    • combat.ts: 移除本地重复常量 96,统一直接引用 monster-rooms.ts 中的规范 PACK_RADIUS_PX(64)。
    • wilderness-acts.ts: 将神秘避难所与塔拉夏古墓硬编码的字面量 * 5 统一替换为 * SUB_TILES_PER_TILE(来自 map.ts)。
  3. 接线未覆盖规则与修剪死逻辑:
    • monsters.ts: monsterColumns 改用基于表头检查的动态遍历(支持到 mon25),防止 mod 数据静默截断。
    • monsters.ts: planMonsterGroups 普通怪类型选取依据 kind.rarity 加权(对齐 selectLevelTypes 稀有度分布规则)。
    • monsters.ts: applyEliteModifiers 为 stoneskin (hp 2x)、cursed (dmg 1.2x)、coldenchant (dmg 1.2x)、fireenchant (dmg 1.2x)、lightenchant (dmg 1.2x)、spectralhit (dmg 1.2x)、teleport (speed 1.2x)、magicresistant (hp 1.2x)、aura (dmg 1.2x, speed 1.1x) 实装数值机制。

独立复验记录 (Merged a7e526d)

  • npm run typecheck: 0 错误
  • npx vitest run: 61 passed, 1018 passed, 2 skipped (全绿,新增 15 个测试)
  • npx tsx scripts/verify-packs.ts: 1671/1671 assertions passed
  • 隔离 worktree /usr/local/google/home/taodao/d2w-issue-63 与分支 fix/issue-63 已清理完毕。

TAG=agy
CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1

## ✅ Issue #63 修复与验证完成 (Closes #63) ### 变更总结 1. **修复 5 处逻辑恒真/恒假与参数缺陷**: - `wilderness.ts`: `isRoad(nx, ny)` 越界检测修正为返回 `false`,消除地图边缘非道路格误用道路瓦片 LUT 的 Bug。 - `maze-objects.ts`: 武器/防具架候选房间严格优先走廊房间(`orths.length === 2 || isBarracks`),消除导致走廊优先规则形同虚设的恒真条件。 - `monster-rooms.ts`: 在 `allocatePacksToRooms` 中,当候选非安全区房间为空(例如城镇或纯安全区关卡)时直接返回,禁止将普通怪物刷入城镇/安全区。 - `monster-rooms.ts`: 修正 `partitionMapIntoRooms` 中默认等距单元格高度为 40px(对齐 80×40 等距投影几何)。 - `acts.ts`: `parseTable` 在传入 0 字节或全空表数据时显式抛出 `Error('parseTable: empty table data')`;补充 `Expansion/Siege/temptile.dt1` 的来源注释(Blizzard LOD patch 拼装 Act 5 攻城 DS1 依赖的地面过渡瓦片)。 2. **统一真相源并消除重复硬编码**: - `wilderness.ts`: 提取 `BLOOD_MOOR_RIVER_PRESETS` 与 `BLOOD_MOOR_POND_PRESETS`,消除血腥荒地预设列表在两处的数组副本分歧。 - `combat.ts`: 移除本地重复常量 `96`,统一直接引用 `monster-rooms.ts` 中的规范 `PACK_RADIUS_PX`(64)。 - `wilderness-acts.ts`: 将神秘避难所与塔拉夏古墓硬编码的字面量 `* 5` 统一替换为 `* SUB_TILES_PER_TILE`(来自 `map.ts`)。 3. **接线未覆盖规则与修剪死逻辑**: - `monsters.ts`: `monsterColumns` 改用基于表头检查的动态遍历(支持到 `mon25`),防止 mod 数据静默截断。 - `monsters.ts`: `planMonsterGroups` 普通怪类型选取依据 `kind.rarity` 加权(对齐 `selectLevelTypes` 稀有度分布规则)。 - `monsters.ts`: `applyEliteModifiers` 为 `stoneskin` (hp 2x)、`cursed` (dmg 1.2x)、`coldenchant` (dmg 1.2x)、`fireenchant` (dmg 1.2x)、`lightenchant` (dmg 1.2x)、`spectralhit` (dmg 1.2x)、`teleport` (speed 1.2x)、`magicresistant` (hp 1.2x)、`aura` (dmg 1.2x, speed 1.1x) 实装数值机制。 ### 独立复验记录 (Merged `a7e526d`) - `npm run typecheck`: 0 错误 - `npx vitest run`: 61 passed, 1018 passed, 2 skipped (全绿,新增 15 个测试) - `npx tsx scripts/verify-packs.ts`: 1671/1671 assertions passed - 隔离 worktree `/usr/local/google/home/taodao/d2w-issue-63` 与分支 `fix/issue-63` 已清理完毕。 TAG=agy CONV=2a1934de-30ef-464e-b3f3-fcbcdbbc49f1
Sign in to join this conversation.
No Label
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: troytt/diablo2-web#63
No description provided.