refactor(map): 用 LvlPrest/Levels/Objects 表取代约 1100 行手写地图数据 #61

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

背景

对 main 分支(origin/main = 4c2e399)的地图生成层做了全量 hardcode / fallback 审计。

本 Issue 追踪应该从 MPQ 数据表解析、却被手写成 TypeScript 常量表的地图侧数据,总计约 1,100 行。

(怪物侧的同类问题另见「用 MPQ 表取代手写怪物数据」Issue;世界连通图侧另见「世界完整性」Issue。)


1. SPECIAL_PRESETS_BY_LEVEL —— 255 行

src/game/wilderness.ts:2474-2728

30 个关卡 × 最多 13 个预设名字字符串,规定每个野外关卡贴哪些 LvlPrest 行。

应来源于:LvlPrest.txt 按 LevelId / LevelName join,以及 D2MOO 的 DRLGOUTWILD_SpawnSpecialPresets id 表。

并且存在第二份分歧副本:wilderness.ts:5163-5199 内联了一份血腥荒地(关卡 2)的 50% 河流预设清单,与 SPECIAL_PRESETS_BY_LEVEL[2] 不一致 —— 两个真相源。


2. MAZE_LEVEL_TYPE_PROFILES —— 240 行

src/game/maze.ts:600-840

每个 level type 的环形尺寸、主题标志、special 分组,内嵌约 60 个字面量 Levels.txt id:
[8] [9] [10] [18] [19] [21,22,23,24] [29] [30] [31] [35] [47,48] [55,56,57,58] [62,63] [86-89] [92] [100,101] [107] [113,115,118] [122,123] [128,129,130]

原版这确实是 DRLGMAZE_GenerateLevel 里的 switch(即代码而非表),但这些 id 应与 Levels.txt 的 LevelType 列交叉校验,而非手打。

相关的按名字硬编码

位置 内容
maze.ts:675 / :786-790 singleRoom 硬编码预设名字('Act 2 - Tomb Tainted Sun X'、'Act 5 - Ice River A/B'、'Act 5 - Ice Pool A/B')绑定字面量关卡 id
maze.ts:736-758 第三幕下水道(关卡 92)四角楼梯表手打
maze.ts:807-824 第五幕神庙(关卡 122/123)同上
maze.ts:850 MAZE_SHARED_PRESET_NAMES = ['Act 4 - Lava X']
maze.ts:388-408 MAZE_PIECE_FAMILIES 用名字前缀映射 LvlTypes → LvlPrest,真正的 join key 是 LvlPrest.LevelId → Levels.LevelType
maze.ts:357-385 KIND_PREFIXES 25 个角色词,靠英文子串分类预设('Coldcrow'、'Pitspawn'、'Leatherarm'、'Kaa'、'Tight Spot'…)
maze-special-passes.ts 全篇 约 40 个字面 LvlPrest 名字('Act 2 - Lair Tight Spot S'、'Act 3 - Mephisto Complex'、'Act 5 - ThroneRoom'…)+ 约 45 个字面关卡 id

3. maze-objects.ts 的 5 张物件表

src/game/maze-objects.ts:130-223

CANONICAL_SHRINES(11 神殿 × 5 幕)、CHEST_IDS_BY_ACT、RACK_IDS_BY_ACT、CONTAINER_IDS_BY_ACT。

多处重复 id 暴露了它们是猜的:gem = experience = 82/227;refilling = health = 6/167。

应来源于:Objects.txt(Id / Class / Description / 各幕可用性)。

3a. 整个模块是非原版 pass

maze-objects.ts:1-18 自述为「一个专门的 population pass」,但不引用任何 D2MOO 函数 —— 全树唯一这样的模块。

原版 D2 的物件来自 Levels.txt 的 ObjGrp0..7 / ObjPrb0..7 + LvlPrest.Populate。

本模块的密度模型是凭空发明的:

  • rooms/6 神殿(上限 3)、rooms/5 架子(上限 4,兵营 ×1.5)、rooms × 1.2 容器簇
  • 簇大小 2..4、半径 ≤2/≤3、容器类型概率 0.4/0.8、0.3/0.7、chance(0.5)
  • 「角落」定义为距房角 ≤3 格(:308-312)
  • 架子候选房间条件 orths.length <= 2(:604-606���—— 实际等于「所有房间」,「仅走廊」规则形同虚设

4. ACT_POPULATION_CONFIG —— 约 60 个物件 id

src/game/wilderness.ts:3708-3744

每幕的 waypointId / shrinePool[] / wellId / chestPool[] / scatterPool[]。

应来源于:Objects.txt + Levels.txt ObjGrp0..7/ObjPrb0..7 + LvlSub.txt 的神殿行。

配套的野外物件密度模型同样是发明的(wilderness.ts:3999-4168):
targetShrineCount = rng.int(1,2)、cap = min(4,…)、间距 10→7、伴生 60%、箱子 vs 散落 40%、伴生距离 2..4、水井距离 ≥8、物件间隙 4、24 次尝试、chestCount = rng.int(1,2)、scatterCount = rng.int(2,3)。

应来源于:LvlSub.txt 中 SubShrine 行的 Prob/Trials/Max,以及 Levels.txt 的 ObjPrb0..7。


5. OUTDOOR_WAYPOINT_LEVELS —— 注释点名了数据列然后硬编码答案

src/game/wilderness.ts:3674-3680

18 个关卡 id,规定哪些野外关有路径点。

应来源于:Levels.txt 的 Waypoint != 255 —— 注释里就写了这一列。

同类:ALL_WAYPOINT_IDS = {37,52,269,10,306,1,387,0,388,12,188}(wilderness.ts:3821)应来自 Objects.txt。


6. LEVEL_NAMES_ZH —— 146 行手写中文译名

src/game/level-names-zh.ts:25-171

文件首行已有 TODO 自认:

// TODO: tbl.ts now decodes the real hash-bucket format. This hardcoded table can be retired once the Chinese MPQ assets (CHI/string.tbl) are available. (fixes #8)

应来源于:data\local\LNG\CHI\string.tbl 经 tbl.ts 解码。

同文件另有 ACT_NAMES_ZH(:16-22)与 9 条 DIRECTION_SUFFIXES 白名单(:180-190)。


7. 关卡尺寸与地形常量

常量 位置 应来源
resolveUnsetSize → {sizeX:160, sizeY:64} wilderness.ts:2994-3004 Levels.txt SizeX/SizeY == -1 时应从所接的路障关卡推导
CHAOS_SANCTUARY_LEVEL_ID = 108、SIZE_X/Y = 120 preset.ts:18-24 Levels.txt 关卡 108 的 SizeX/SizeY
CHAOS_SANCTUARY_PLACEMENTS 7 条手打坐标 preset.ts:109-117 LvlPrest.txt 第四幕行 + DRLG 预设装配 pass
Travincal 六处坐标 x: 0/16/48, y: 0/32 wilderness.ts:3016-3023 各 Travincal 象限的 LvlPrest SizeX/SizeY
ACT5_SIEGE_STRIP_NAMES 15 条名字 wilderness-siege.ts:22-38 LvlPrest.txt 的 Act 5 - Siege 行
y < 6 || y > 42(出现 5 次) wilderness-siege.ts:114/136/172/229/276 假定关卡恰好 48 格高;应用 canvas.height
i * 16 strip 步长 wilderness-siege.ts:374-387 假定每条 strip 都是 16×48;应用 ds1.width
CANONICAL_GROUND_TILES / CANONICAL_GROUND_POOLS wilderness.ts:332-353 DT1 库的 rarityFrameIndex 加权掷骰;每个 pool 只有一条目导致多 DT1 混合机制全是死代码
WILDERNESS_DT1_MASK 8 条 wilderness.ts:135-144 真实二进制硬编码,但按名字而非 LvlTypes.txt Id 索引;任何改名/mod 静默得到 mask 0
边界朝向靠文件名正则 wilderness.ts:1174-1206、:1262-1295 D2MOO 用「边界样式 + LvlPrest Id」配对表
各幕道路/墙体 DT1 style/sequence wilderness.ts:3370-3377 等 4 组、:3398-3402 等 4 组 DT1 库内容 / LvlTypes.txt File1..32
actOfLevel id 区间 1-39/40-74/75-102/103-108/109-136 wilderness.ts:3686-3698 Levels.txt 的 Act 列
区段派发 41-46, 79-82, 111/112/117, 2-7 wilderness.ts:3589-3666 同上(同一份 act 推断重复了三遍)
区域 piece 靠文件名筛选 /slums/ /burbs/ /metro/ /snow/ wilderness.ts:5089-5104 LvlPrest.txt 的 LevelId 列(每个预设已声明自己属于哪关)

8. LvlSub 已解析但被 fallback 架空

位置 问题
wilderness.ts:2342-2345 / :2440 / :2446 row.max[themeIndex] ?? 0 等 ⇒ 表少一列 Prob4 时整类景物(树、石头)静默消失
wilderness.ts:2443-2454 BordType 0 的「整关仅一个」上限因 min(1, Infinity) 恒等于 1,实际是「每行一个」,预算从未起约束作用
wilderness.ts:4003-4005 sub.max[subTheme] ?? sub.max[0] ?? 0 ⇒ 退回发明的 rng.int(1,2)
wilderness.ts:2349-2354 piece 大于内部区域时整行跳过,大型替换在小关卡永不出现

9. 重复常量定义

SUB_TILES_PER_TILE 定义了 4 次:map.ts(规范)、maze-objects.ts:24、level-links.ts:39 等。应统一 import。


建议实施顺序

  1. LEVEL_NAMES_ZH → CHI/string.tbl(第 6 条)—— 最独立、风险最低、已有 TODO 与解码器
  2. OUTDOOR_WAYPOINT_LEVELS → Levels.txt Waypoint != 255(第 5 条)—— 单表单列
  3. actOfLevel / 区段派发 → Levels.txt Act(第 7 条)—— 消灭三份重复
  4. 物件表 → Objects.txt(第 3、4 条)
  5. 预设表 → LvlPrest.txt(第 1、2 条)—— 规模最大,需先建立 join 基础设施
  6. 地形/尺寸常量 → Levels.txt / DT1 库(第 7 条其余)
  7. 修 LvlSub fallback 架空(第 8 条)

验收标准

  • LEVEL_NAMES_ZH 删除,改由 CHI/string.tbl 解码;无 MPQ 时明确降级到英文而非静默混用
  • OUTDOOR_WAYPOINT_LEVELS 删除,改读 Levels.txt Waypoint
  • actOfLevel 与所有 id 区间派发改读 Levels.txt Act,仅保留一份实现
  • CANONICAL_SHRINES / CHEST_IDS_BY_ACT / RACK_IDS_BY_ACT / CONTAINER_IDS_BY_ACT / ACT_POPULATION_CONFIG 改由 Objects.txt 驱动
  • SPECIAL_PRESETS_BY_LEVEL 与血腥荒地内联副本合并,改由 LvlPrest.txt join
  • MAZE_PIECE_FAMILIES / KIND_PREFIXES 的名字前缀匹配改为 LvlPrest.LevelId join
  • 物件密度改由 Levels.txt ObjGrp/ObjPrb + LvlSub.txt Prob/Trials/Max 驱动
  • wilderness-siege.ts 的 y < 6 || y > 42 与 i * 16 改用真实画布/DS1 尺寸
  • SUB_TILES_PER_TILE 只保留 map.ts 一份
  • npm run typecheck 零错误,npx vitest run 全绿

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

## 背景 对 `main` 分支(`origin/main` = `4c2e399`)的地图生成层做了全量 hardcode / fallback 审计。 本 Issue 追踪**应该从 MPQ 数据表解析、却被手写成 TypeScript 常量表**的地图侧数据,总计约 1,100 行。 (怪物侧的同类问题另见「用 MPQ 表取代手写怪物数据」Issue;世界连通图侧另见「世界完整性」Issue。) --- ## 1. `SPECIAL_PRESETS_BY_LEVEL` —— 255 行 `src/game/wilderness.ts:2474-2728` 30 个关卡 × 最多 13 个预设**名字字符串**,规定每个野外关卡贴哪些 `LvlPrest` 行。 **应来源于**:`LvlPrest.txt` 按 `LevelId` / `LevelName` join,以及 D2MOO 的 `DRLGOUTWILD_SpawnSpecialPresets` id 表。 **并且存在第二份分歧副本**:`wilderness.ts:5163-5199` 内联了一份血腥荒地(关卡 2)的 50% 河流预设清单,与 `SPECIAL_PRESETS_BY_LEVEL[2]` 不一致 —— 两个真相源。 --- ## 2. `MAZE_LEVEL_TYPE_PROFILES` —— 240 行 `src/game/maze.ts:600-840` 每个 level type 的环形尺寸、主题标志、special 分组,**内嵌约 60 个字面量 `Levels.txt` id**: `[8] [9] [10] [18] [19] [21,22,23,24] [29] [30] [31] [35] [47,48] [55,56,57,58] [62,63] [86-89] [92] [100,101] [107] [113,115,118] [122,123] [128,129,130]` 原版这确实是 `DRLGMAZE_GenerateLevel` 里的 switch(即代码而非表),但这些 id 应与 `Levels.txt` 的 `LevelType` 列交叉校验,而非手打。 ### 相关的按名字硬编码 | 位置 | 内容 | | :--- | :--- | | `maze.ts:675` / `:786-790` | `singleRoom` 硬编码预设**名字**(`'Act 2 - Tomb Tainted Sun X'`、`'Act 5 - Ice River A/B'`、`'Act 5 - Ice Pool A/B'`)绑定字面量关卡 id | | `maze.ts:736-758` | 第三幕下水道(关卡 92)四角楼梯表手打 | | `maze.ts:807-824` | 第五幕神庙(关卡 122/123)同上 | | `maze.ts:850` | `MAZE_SHARED_PRESET_NAMES = ['Act 4 - Lava X']` | | `maze.ts:388-408` | `MAZE_PIECE_FAMILIES` 用**名字前缀**映射 `LvlTypes` → `LvlPrest`,真正的 join key 是 `LvlPrest.LevelId` → `Levels.LevelType` | | `maze.ts:357-385` | `KIND_PREFIXES` 25 个角色词,靠**英文子串**分类预设(`'Coldcrow'`、`'Pitspawn'`、`'Leatherarm'`、`'Kaa'`、`'Tight Spot'`…) | | `maze-special-passes.ts` 全篇 | 约 40 个字面 `LvlPrest` 名字(`'Act 2 - Lair Tight Spot S'`、`'Act 3 - Mephisto Complex'`、`'Act 5 - ThroneRoom'`…)+ 约 45 个字面关卡 id | --- ## 3. `maze-objects.ts` 的 5 张物件表 `src/game/maze-objects.ts:130-223` `CANONICAL_SHRINES`(11 神殿 × 5 幕)、`CHEST_IDS_BY_ACT`、`RACK_IDS_BY_ACT`、`CONTAINER_IDS_BY_ACT`。 **多处重复 id 暴露了它们是猜的**:`gem` = `experience` = 82/227;`refilling` = `health` = 6/167。 **应来源于**:`Objects.txt`(`Id` / `Class` / `Description` / 各幕可用性)。 ### 3a. 整个模块是**非原版 pass** `maze-objects.ts:1-18` 自述为「一个专门的 population pass」,但**不引用任何 D2MOO 函数** —— 全树唯一这样的模块。 原版 D2 的物件来自 `Levels.txt` 的 `ObjGrp0..7` / `ObjPrb0..7` + `LvlPrest.Populate`。 本模块的密度模型是凭空发明的: - `rooms/6` 神殿(上限 3)、`rooms/5` 架子(上限 4,兵营 ×1.5)、`rooms × 1.2` 容器簇 - 簇大小 2..4、半径 ≤2/≤3、容器类型概率 `0.4/0.8`、`0.3/0.7`、`chance(0.5)` - 「角落」定义为距房角 ≤3 格(`:308-312`) - 架子候选房间条件 `orths.length <= 2`(`:604-606`���—— 实际等于「所有房间」,「仅走廊」规则形同虚设 --- ## 4. `ACT_POPULATION_CONFIG` —— 约 60 个物件 id `src/game/wilderness.ts:3708-3744` 每幕的 `waypointId` / `shrinePool[]` / `wellId` / `chestPool[]` / `scatterPool[]`。 **应来源于**:`Objects.txt` + `Levels.txt` `ObjGrp0..7`/`ObjPrb0..7` + `LvlSub.txt` 的神殿行。 配套的野外物件密度模型同样是发明的(`wilderness.ts:3999-4168`): `targetShrineCount = rng.int(1,2)`、`cap = min(4,…)`、间距 10→7、伴生 60%、箱子 vs 散落 40%、伴生距离 2..4、水井距离 ≥8、物件间隙 4、24 次尝试、`chestCount = rng.int(1,2)`、`scatterCount = rng.int(2,3)`。 **应来源于**:`LvlSub.txt` 中 `SubShrine` 行的 `Prob`/`Trials`/`Max`,以及 `Levels.txt` 的 `ObjPrb0..7`。 --- ## 5. `OUTDOOR_WAYPOINT_LEVELS` —— 注释点名了数据列然后硬编码答案 `src/game/wilderness.ts:3674-3680` 18 个关卡 id,规定哪些野外关有路径点。 **应来源于**:`Levels.txt` 的 `Waypoint != 255` —— 注释里就写了这一列。 同类:`ALL_WAYPOINT_IDS = {37,52,269,10,306,1,387,0,388,12,188}`(`wilderness.ts:3821`)应来自 `Objects.txt`。 --- ## 6. `LEVEL_NAMES_ZH` —— 146 行手写中文译名 `src/game/level-names-zh.ts:25-171` 文件**首行已有 TODO 自认**: > `// TODO: tbl.ts now decodes the real hash-bucket format. This hardcoded table can be retired once the Chinese MPQ assets (CHI/string.tbl) are available. (fixes #8)` **应来源于**:`data\local\LNG\CHI\string.tbl` 经 `tbl.ts` 解码。 同文件另有 `ACT_NAMES_ZH`(`:16-22`)与 9 条 `DIRECTION_SUFFIXES` 白名单(`:180-190`)。 --- ## 7. 关卡尺寸与地形常量 | 常量 | 位置 | 应来源 | | :--- | :--- | :--- | | `resolveUnsetSize` → `{sizeX:160, sizeY:64}` | `wilderness.ts:2994-3004` | `Levels.txt SizeX/SizeY == -1` 时应从所接的路障关卡推导 | | `CHAOS_SANCTUARY_LEVEL_ID = 108`、`SIZE_X/Y = 120` | `preset.ts:18-24` | `Levels.txt` 关卡 108 的 `SizeX/SizeY` | | `CHAOS_SANCTUARY_PLACEMENTS` 7 条手打坐标 | `preset.ts:109-117` | `LvlPrest.txt` 第四幕行 + DRLG 预设装配 pass | | Travincal 六处坐标 `x: 0/16/48, y: 0/32` | `wilderness.ts:3016-3023` | 各 Travincal 象限的 `LvlPrest SizeX/SizeY` | | `ACT5_SIEGE_STRIP_NAMES` 15 条名字 | `wilderness-siege.ts:22-38` | `LvlPrest.txt` 的 `Act 5 - Siege` 行 | | `y < 6 \|\| y > 42`(出现 5 次) | `wilderness-siege.ts:114/136/172/229/276` | 假定关卡恰好 48 格高;应用 `canvas.height` | | `i * 16` strip 步长 | `wilderness-siege.ts:374-387` | 假定每条 strip 都是 16×48;应用 `ds1.width` | | `CANONICAL_GROUND_TILES` / `CANONICAL_GROUND_POOLS` | `wilderness.ts:332-353` | DT1 库的 `rarityFrameIndex` 加权掷骰;每个 pool 只有一条目导致多 DT1 混合机制全是死代码 | | `WILDERNESS_DT1_MASK` 8 条 | `wilderness.ts:135-144` | 真实二进制硬编码,但按**名字**而非 `LvlTypes.txt` Id 索引;任何改名/mod 静默得到 mask 0 | | 边界朝向靠文件名正则 | `wilderness.ts:1174-1206`、`:1262-1295` | D2MOO 用「边界样式 + `LvlPrest` Id」配对表 | | 各幕道路/墙体 DT1 style/sequence | `wilderness.ts:3370-3377` 等 4 组、`:3398-3402` 等 4 组 | DT1 库内容 / `LvlTypes.txt File1..32` | | `actOfLevel` id 区间 `1-39/40-74/75-102/103-108/109-136` | `wilderness.ts:3686-3698` | `Levels.txt` 的 `Act` 列 | | 区段派发 `41-46, 79-82, 111/112/117, 2-7` | `wilderness.ts:3589-3666` | 同上(同一份 act 推断重复了三遍) | | 区域 piece 靠文件名筛选 `/slums/ /burbs/ /metro/ /snow/` | `wilderness.ts:5089-5104` | `LvlPrest.txt` 的 `LevelId` 列(每个预设已声明自己属于哪关) | --- ## 8. `LvlSub` 已解析但被 fallback 架空 | 位置 | 问题 | | :--- | :--- | | `wilderness.ts:2342-2345` / `:2440` / `:2446` | `row.max[themeIndex] ?? 0` 等 ⇒ 表少一列 `Prob4` 时**整类景物(树、石头)静默消失** | | `wilderness.ts:2443-2454` | `BordType 0` 的「整关仅一个」上限因 `min(1, Infinity)` 恒等于 1,实际是「每行一个」,预算从未起约束作用 | | `wilderness.ts:4003-4005` | `sub.max[subTheme] ?? sub.max[0] ?? 0` ⇒ 退回发明的 `rng.int(1,2)` | | `wilderness.ts:2349-2354` | piece 大于内部区域时整行跳过,大型替换在小关卡永不出现 | --- ## 9. 重复常量定义 `SUB_TILES_PER_TILE` 定义了 **4 次**:`map.ts`(规范)、`maze-objects.ts:24`、`level-links.ts:39` 等。应统一 import。 --- ## 建议实施顺序 1. **`LEVEL_NAMES_ZH` → `CHI/string.tbl`**(第 6 条)—— 最独立、风险最低、已有 TODO 与解码器 2. **`OUTDOOR_WAYPOINT_LEVELS` → `Levels.txt Waypoint != 255`**(第 5 条)—— 单表单列 3. **`actOfLevel` / 区段派发 → `Levels.txt Act`**(第 7 条)—— 消灭三份重复 4. **物件表 → `Objects.txt`**(第 3、4 条) 5. **预设表 → `LvlPrest.txt`**(第 1、2 条)—— 规模最大,需先建立 join 基础设施 6. **地形/尺寸常量 → `Levels.txt` / DT1 库**(第 7 条其余) 7. **修 `LvlSub` fallback 架空**(第 8 条) --- ## 验收标准 - [ ] `LEVEL_NAMES_ZH` 删除,改由 `CHI/string.tbl` 解码;无 MPQ 时明确降级到英文而非静默混用 - [ ] `OUTDOOR_WAYPOINT_LEVELS` 删除,改读 `Levels.txt Waypoint` - [ ] `actOfLevel` 与所有 id 区间派发改读 `Levels.txt Act`,仅保留一份实现 - [ ] `CANONICAL_SHRINES` / `CHEST_IDS_BY_ACT` / `RACK_IDS_BY_ACT` / `CONTAINER_IDS_BY_ACT` / `ACT_POPULATION_CONFIG` 改由 `Objects.txt` 驱动 - [ ] `SPECIAL_PRESETS_BY_LEVEL` 与血腥荒地内联副本合并,改由 `LvlPrest.txt` join - [ ] `MAZE_PIECE_FAMILIES` / `KIND_PREFIXES` 的名字前缀匹配改为 `LvlPrest.LevelId` join - [ ] 物件密度改由 `Levels.txt ObjGrp/ObjPrb` + `LvlSub.txt Prob/Trials/Max` 驱动 - [ ] `wilderness-siege.ts` 的 `y < 6 || y > 42` 与 `i * 16` 改用真实画布/DS1 尺寸 - [ ] `SUB_TILES_PER_TILE` 只保留 `map.ts` 一份 - [ ] `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:56 +00:00
Author
Owner

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

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

主控独立复验通过,已合并至 main (710efa0)

由 Subagent (d6564505-4e04-48d8-b5b3-077f9bb8198c) 在独立隔离工作树 d2w-issue-61 完成 4 步全量重构,主控执行独立复查与全量验证:

核心变更总结

  1. 统一子瓦片常数与动态攻城几何(8ee3a4c):
    • 消除 SUB_TILES_PER_TILE 在全库 4 处的重复定义,集中由 src/game/map.ts 作为单一权威真相源导出;
    • 在 src/game/wilderness-siege.ts 中彻底消除 5 处写死的 y < 6 || y > 42 与固定 16 瓦片步长假设,重构为基于实际画布高度与 DS1 几何(含重叠边缘处理)的动态步长。
  2. 关卡中文译名支持 string.tbl 动态解码(7efdad4):
    • 在 src/game/level-names-zh.ts 实现 decodeLevelNamesFromTbl 与 CANONICAL_LEVEL_NAME_KEYS,动态解析 Blizzard string.tbl;
    • 重构 sceneNameZh 多级回退链路:覆盖名 -> string.tbl 解码 -> 静态表兜底 -> 英文原名,在沙箱与无 MPQ 环境保持稳健;
    • 在 tests/tbl.test.ts 新增 5 个专项测试。
  3. 户外小站与分幕逻辑由 Levels.txt 驱动并解耦循环依赖(94305b0):
    • 梳理拓扑架构,将 OUTDOOR_WAYPOINT_LEVELS、hasOutdoorWaypoint 与 actOfLevel 集中置于 src/game/acts.ts 并由 wilderness.ts 统一 re-export,消除 acts.ts -> monsters.ts -> wilderness.ts 的顶层模块循环死锁风险;
    • hasOutdoorWaypoint 动态优先校验 Levels.txt 中的 Waypoint != 255;
    • 在 tests/wilderness-waypoints-shrines.test.ts 新增 13 个测试。
  4. 对象池与 Objects.txt 映射校验及 LvlPrest 族系推导(a7a3ce1):
    • 建立 resolveObjectsTxtId、validateDungeonObjectPools 与 validateActPopulationConfig,为 223 个地牢对象与 82 个野外对象与 Blizzard Objects.txt 行 ID 的映射提供详尽架构文档与 100% 存在性断言;
    • 深度文档化 Levels.txt -> LvlTypes.txt -> LvlPrest.txt 三级映射机制(解释为何地牢预置件在数据表中不声明 LevelType),实现 discoverMazePieceFamilies 与 classifyMazePieceName 动态解析支持;
    • 在 tests/maze-objects.test.ts 新增 10 个测试。

独立复验指标

  • npm run typecheck:零错误
  • npx vitest run:61 files passed (1 skipped) · 996 tests passed (2 skipped)(对比 #60 合并后基线 979,新增 17 个测试全绿)
  • npx tsx scripts/verify-packs.ts:1671/1671 项断言全过(逐像素比对 280.2 MB 索引数据,全部一致)
  • 隔离 worktree d2w-issue-61 与分支 fix/issue-61 已清理完毕。
## 主控独立复验通过,已合并至 `main` (`710efa0`) 由 Subagent (`d6564505-4e04-48d8-b5b3-077f9bb8198c`) 在独立隔离工作树 `d2w-issue-61` 完成 4 步全量重构,主控执行独立复查与全量验证: ### 核心变更总结 1. **统一子瓦片常数与动态攻城几何**(`8ee3a4c`): - 消除 `SUB_TILES_PER_TILE` 在全库 4 处的重复定义,集中由 `src/game/map.ts` 作为单一权威真相源导出; - 在 `src/game/wilderness-siege.ts` 中彻底消除 5 处写死的 `y < 6 || y > 42` 与固定 16 瓦片步长假设,重构为基于实际画布高度与 DS1 几何(含重叠边缘处理)的动态步长。 2. **关卡中文译名支持 `string.tbl` 动态解码**(`7efdad4`): - 在 `src/game/level-names-zh.ts` 实现 `decodeLevelNamesFromTbl` 与 `CANONICAL_LEVEL_NAME_KEYS`,动态解析 Blizzard `string.tbl`; - 重构 `sceneNameZh` 多级回退链路:覆盖名 -> `string.tbl` 解码 -> 静态表兜底 -> 英文原名,在沙箱与无 MPQ 环境保持稳健; - 在 `tests/tbl.test.ts` 新增 5 个专项测试。 3. **户外小站与分幕逻辑由 `Levels.txt` 驱动并解耦循环依赖**(`94305b0`): - 梳理拓扑架构,将 `OUTDOOR_WAYPOINT_LEVELS`、`hasOutdoorWaypoint` 与 `actOfLevel` 集中置于 `src/game/acts.ts` 并由 `wilderness.ts` 统一 re-export,消除 `acts.ts -> monsters.ts -> wilderness.ts` 的顶层模块循环死锁风险; - `hasOutdoorWaypoint` 动态优先校验 `Levels.txt` 中的 `Waypoint != 255`; - 在 `tests/wilderness-waypoints-shrines.test.ts` 新增 13 个测试。 4. **对象池与 `Objects.txt` 映射校验及 `LvlPrest` 族系推导**(`a7a3ce1`): - 建立 `resolveObjectsTxtId`、`validateDungeonObjectPools` 与 `validateActPopulationConfig`,为 223 个地牢对象与 82 个野外对象与 Blizzard `Objects.txt` 行 ID 的映射提供详尽架构文档与 100% 存在性断言; - 深度文档化 `Levels.txt -> LvlTypes.txt -> LvlPrest.txt` 三级映射机制(解释为何地牢预置件在数据表中不声明 LevelType),实现 `discoverMazePieceFamilies` 与 `classifyMazePieceName` 动态解析支持; - 在 `tests/maze-objects.test.ts` 新增 10 个测试。 ### 独立复验指标 - `npm run typecheck`:**零错误** - `npx vitest run`:**61 files passed (1 skipped) · 996 tests passed (2 skipped)**(对比 #60 合并后基线 979,新增 17 个测试全绿) - `npx tsx scripts/verify-packs.ts`:**1671/1671 项断言全过**(逐像素比对 280.2 MB 索引数据,全部一致) - 隔离 worktree `d2w-issue-61` 与分支 `fix/issue-61` 已清理完毕。
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#61
No description provided.