[DRLG-M1] 核心迷宫专用布局例程与基于 LvlPrest ID 的精准预设替换 #73

Closed
opened 2026-09-18 06:36:13 +00:00 by troytt · 1 comment
Owner

目标与背景

以暴雪原版 1.13c D2Common.dll (Ordinal 10006 DRLG_InitLevel, Ordinal 10008 DRLGPRESET_GetLevelPrestIdFromRoomEx, RVA 0x52660) 及 DRLGMAZE_GenerateLevel 为黄金标准,彻底重构现行 maze-special-passes.ts 与 maze.ts。

缺陷与审计发现

  1. 模糊字符串匹配风险: 当前使用 p.name.startsWith(namePrefix) 猜测预设,容易因命名、大小写、空格差异导致特殊房间替换失败。
  2. 缺失关键迷宫专属布局分支:
    • 爪蝮蛇神殿2层 (LEVEL_CLAWVIPERTEMPLELEV2): 固定放置太阳祭坛大厅 (LVLPREST_ACT2_TOMB_TAINTED_SUN_X);
    • 库拉斯特下水道1层 (LEVEL_SEWERSA3LEV1): 初始化 5 房间网格并强制替换 SW/SE/NW/NE 四角出口;
    • 漂流者洞穴/冰河 (LEVEL_CELLAROFPITY): 按种子在 ICE_RIVER_A 与 ICE_RIVER_B 之间随机决定拯救安雅河流;
    • 尼拉塞克神殿 (LVLTYPE_ACT5_TEMPLE): 严格执行 1.13c 硬编码四向重映射表 (nExpansionHardcodedPresetsRemapping);
    • 军营恰西铁砧与大教堂连通口 (DRLGMAZE_PlaceAct1Barracks);
    • 火焰之河/地狱熔炉与大桥布局 (DRLGMAZE_PlaceAct4Lava)。
  3. 房间合并与环路生成: 完善 DRLGMAZE_MergeMazeRooms 的二维 Orth 邻接与基于 LvlMaze.txt.Merge (dwMerge / 1000) 的动态门框成环逻辑。

验收条件

  • 废弃模糊字符串前缀匹配,建立 LvlPrest.txt 数值 ID 精确映射表。
  • 实现上述缺失的 1.13c 迷宫专用布局分支。
  • npm run typecheck 0 errors。
  • npx vitest run 全量通过并新增针对 1.13c 典型种子的特征比对测试。
  • npx tsx scripts/verify-packs.ts 1671/1671 项逐字节完全匹配。
### 目标与背景 以暴雪原版 1.13c `D2Common.dll` (Ordinal 10006 `DRLG_InitLevel`, Ordinal 10008 `DRLGPRESET_GetLevelPrestIdFromRoomEx`, RVA `0x52660`) 及 `DRLGMAZE_GenerateLevel` 为黄金标准,彻底重构现行 `maze-special-passes.ts` 与 `maze.ts`。 ### 缺陷与审计发现 1. **模糊字符串匹配风险**: 当前使用 `p.name.startsWith(namePrefix)` 猜测预设,容易因命名、大小写、空格差异导致特殊房间替换失败。 2. **缺失关键迷宫专属布局分支**: - 爪蝮蛇神殿2层 (`LEVEL_CLAWVIPERTEMPLELEV2`): 固定放置太阳祭坛大厅 (`LVLPREST_ACT2_TOMB_TAINTED_SUN_X`); - 库拉斯特下水道1层 (`LEVEL_SEWERSA3LEV1`): 初始化 5 房间网格并强制替换 SW/SE/NW/NE 四角出口; - 漂流者洞穴/冰河 (`LEVEL_CELLAROFPITY`): 按种子在 `ICE_RIVER_A` 与 `ICE_RIVER_B` 之间随机决定拯救安雅河流; - 尼拉塞克神殿 (`LVLTYPE_ACT5_TEMPLE`): 严格执行 1.13c 硬编码四向重映射表 (`nExpansionHardcodedPresetsRemapping`); - 军营恰西铁砧与大教堂连通口 (`DRLGMAZE_PlaceAct1Barracks`); - 火焰之河/地狱熔炉与大桥布局 (`DRLGMAZE_PlaceAct4Lava`)。 3. **房间合并与环路生成**: 完善 `DRLGMAZE_MergeMazeRooms` 的二维 Orth 邻接与基于 `LvlMaze.txt.Merge` (dwMerge / 1000) 的动态门框成环逻辑。 ### 验收条件 - [ ] 废弃模糊字符串前缀匹配,建立 `LvlPrest.txt` 数值 ID 精确映射表。 - [ ] 实现上述缺失的 1.13c 迷宫专用布局分支。 - [ ] `npm run typecheck` 0 errors。 - [ ] `npx vitest run` 全量通过并新增针对 1.13c 典型种子的特征比对测试。 - [ ] `npx tsx scripts/verify-packs.ts` 1671/1671 项逐字节完全匹配。
troytt added this to the [M17] 核心地下城与碰撞层原版一致性 (1.13c) milestone 2026-09-18 06:36:13 +00:00
Author
Owner

✅ 已修复并合并至 main (e64e9a9)

以 Diablo II v1.13c D2Common.dll (MD5 ee1238806ef6d6d9801d12a09d128fe1) 为黄金标准,对齐 DRLGMAZE_Place*Stuff 系列核心迷宫专用布局例程。

实现内容

1. LvlPrest Def ID 精准替换机制

  • 新增 1.13c 4 位门洞位掩码常量 MAZE_DOOR_WEST=1 / EAST=2 / SOUTH=4 / NORTH=8,以及 mazeDoorMask(room) 与 mazePresetDefIdForDoorMask(baseDefId, room)(原版公式 baseDefId + doorMask - 1)。
  • 补齐 LvlPrest.txt 规范 Def ID 常量:
    • Act2 墓穴:Talrasha 625、Kaa 629、Tainted Sun X 633
    • Act3 下水道:SW/SE/NW/NE 585–588、Drain 589、Chest 593
    • Act5 冰洞:Prev 1018、Waypoint 1026、River A/B 1030/1031、Pool A/B 1032/1033
    • Act5 神殿:remapping base 1044、Final Room 1059、Nihlathak 侧翼 base 1060
  • 新增 LVLPREST_MAZE_DEF_REGISTRY 与 replaceRoomByPresetDefId(),替换结果以 lvlPrestDef=<id> (<name>) 形式写入 stats.notes,便于逐关卡验证。

2. 各 Act 专用布局例程对齐

  • placeAct2TombStuff:新增 levelId 61(爪蛇神庙 2)分支,固定放置太阳祭坛 Def 633。
  • placeAct3SewerStuff:levelId 92 放置四角入口房 585–588,Drain/Chest 按门洞方向偏移。
  • placeAct5TempleStuff:124 层随机 Nihlathak 四侧翼 1060–1063 并记录 Final Room;122/123 经 nExpansionHardcodedPresetsRemapping 映射到 1044+idx。
  • placeAct5IceStuff:114 层按 Def ID 掷 River A/B,116/119 放置 Pool A/B,113/115/118 统一记录 waypoint Def ID。

顺带修复的两处潜在保真度缺陷

⚠️ runMazeSpecialPasses 从未在单房间关卡执行
该调用原先嵌套在 singleRoom 分支的 else 中,导致 61(爪蛇神庙 2)、114(冰封河道)、116/119(漂流洞穴/冰窖)等关卡的专用布局例程被完全跳过。现已提出至外层。

⚠️ inferLevelTypeName 无法命中冰洞 profile
该函数取 piece 名的最长公共前缀,Act 5 - Ice ... 系列会坍缩为 'Act 5 - Ice',与 profile 名 'Act 5 - Ice Caves' 不匹配。已增加显式前缀别名(未改 profile 名,避免波及 pack 流水线)。

验证

新增 tests/maze-113c.test.ts(5 个用例):门洞掩码运算、爪蛇神庙 2 Def 633、下水道角房与 Drain、冰河 A/B 跨 12 seed 分布与水池、神殿 remapping 与侧翼掷骰。

门禁 结果
npm run typecheck ✅ 0 errors
npx vitest run ✅ 67 files / 1119 passed / 2 skipped
npx tsx scripts/verify-packs.ts ✅ 1671/1671 项断言通过(逐像素比对 280.2 MB)
## ✅ 已修复并合并至 main (`e64e9a9`) 以 Diablo II v1.13c `D2Common.dll` (MD5 `ee1238806ef6d6d9801d12a09d128fe1`) 为黄金标准,对齐 `DRLGMAZE_Place*Stuff` 系列核心迷宫专用布局例程。 ### 实现内容 **1. LvlPrest Def ID 精准替换机制** - 新增 1.13c 4 位门洞位掩码常量 `MAZE_DOOR_WEST=1 / EAST=2 / SOUTH=4 / NORTH=8`,以及 `mazeDoorMask(room)` 与 `mazePresetDefIdForDoorMask(baseDefId, room)`(原版公式 `baseDefId + doorMask - 1`)。 - 补齐 `LvlPrest.txt` 规范 `Def` ID 常量: - Act2 墓穴:Talrasha `625`、Kaa `629`、Tainted Sun X `633` - Act3 下水道:SW/SE/NW/NE `585`–`588`、Drain `589`、Chest `593` - Act5 冰洞:Prev `1018`、Waypoint `1026`、River A/B `1030`/`1031`、Pool A/B `1032`/`1033` - Act5 神殿:remapping base `1044`、Final Room `1059`、Nihlathak 侧翼 base `1060` - 新增 `LVLPREST_MAZE_DEF_REGISTRY` 与 `replaceRoomByPresetDefId()`,替换结果以 `lvlPrestDef=<id> (<name>)` 形式写入 `stats.notes`,便于逐关卡验证。 **2. 各 Act 专用布局例程对齐** - `placeAct2TombStuff`:新增 `levelId 61`(爪蛇神庙 2)分支,固定放置太阳祭坛 Def `633`。 - `placeAct3SewerStuff`:`levelId 92` 放置四角入口房 `585`–`588`,Drain/Chest 按门洞方向偏移。 - `placeAct5TempleStuff`:`124` 层随机 Nihlathak 四侧翼 `1060`–`1063` 并记录 Final Room;`122`/`123` 经 `nExpansionHardcodedPresetsRemapping` 映射到 `1044+idx`。 - `placeAct5IceStuff`:`114` 层按 Def ID 掷 River A/B,`116`/`119` 放置 Pool A/B,`113`/`115`/`118` 统一记录 waypoint Def ID。 ### 顺带修复的两处潜在保真度缺陷 > ⚠️ **`runMazeSpecialPasses` 从未在单房间关卡执行** > 该调用原先嵌套在 `singleRoom` 分支的 `else` 中,导致 `61`(爪蛇神庙 2)、`114`(冰封河道)、`116`/`119`(漂流洞穴/冰窖)等关卡的专用布局例程被**完全跳过**。现已提出至外层。 > ⚠️ **`inferLevelTypeName` 无法命中冰洞 profile** > 该函数取 piece 名的最长公共前缀,`Act 5 - Ice ...` 系列会坍缩为 `'Act 5 - Ice'`,与 profile 名 `'Act 5 - Ice Caves'` 不匹配。已增加显式前缀别名(未改 profile 名,避免波及 pack 流水线)。 ### 验证 新增 `tests/maze-113c.test.ts`(5 个用例):门洞掩码运算、爪蛇神庙 2 Def `633`、下水道角房与 Drain、冰河 A/B 跨 12 seed 分布与水池、神殿 remapping 与侧翼掷骰。 | 门禁 | 结果 | |---|---| | `npm run typecheck` | ✅ 0 errors | | `npx vitest run` | ✅ 67 files / **1119 passed** / 2 skipped | | `npx tsx scripts/verify-packs.ts` | ✅ **1671/1671** 项断言通过(逐像素比对 280.2 MB)|
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#73
No description provided.