[DRLG-M5] 还原相邻 Room 边界消隐去重 (bKillEdge) 与门洞平滑衔接 #77

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

目标与背景

以暴雪原版 1.13c D2Common.dll DRLGROOMTILE_LoadInitRoomTiles 与 RoomTile.cpp 为黄金标准,重构相邻房间缝合逻辑。

缺陷与审计发现

  1. 缺失相邻房间接缝裁剪 (bKillEdge): 原版 1.13c 在加载拼接相邻 Room 时,计算 nTileCountX = pDrlgRoom->nTileWidth + (bKillEdgeX == 0),动态裁剪外边界重复墙体。当前引擎缺乏该消隐机制,相邻 Room 接缝处存在图元重复绘制与缝隙隐患。
  2. 门洞隐藏与过道地砖补充不足: 原版打通门洞时,将原墙体标记为 bHidden 并自动补充 6 块门槛过道 Floor 地砖 (pTiles.nFloors += 6)。当前引擎缺乏该补充,偶现门洞边缘轻微地面镂空。

验收条件

  • 实现 bKillEdgeX 与 bKillEdgeY 边界去重消隐算法。
  • 实现门洞 bHidden 墙体消隐与门槛过道地砖补充。
  • npm run typecheck 0 errors。
  • npx vitest run 全量通过。
  • npx tsx scripts/verify-packs.ts 1671/1671 项逐字节完全匹配。
### 目标与背景 以暴雪原版 1.13c `D2Common.dll` `DRLGROOMTILE_LoadInitRoomTiles` 与 `RoomTile.cpp` 为黄金标准,重构相邻房间缝合逻辑。 ### 缺陷与审计发现 1. **缺失相邻房间接缝裁剪 (`bKillEdge`)**: 原版 1.13c 在加载拼接相邻 Room 时,计算 `nTileCountX = pDrlgRoom->nTileWidth + (bKillEdgeX == 0)`,动态裁剪外边界重复墙体。当前引擎缺乏该消隐机制,相邻 Room 接缝处存在图元重复绘制与缝隙隐患。 2. **门洞隐藏与过道地砖补充不足**: 原版打通门洞时,将原墙体标记为 `bHidden` 并自动补充 6 块门槛过道 Floor 地砖 (`pTiles.nFloors += 6`)。当前引擎缺乏该补充,偶现门洞边缘轻微地面镂空。 ### 验收条件 - [ ] 实现 `bKillEdgeX` 与 `bKillEdgeY` 边界去重消隐算法。 - [ ] 实现门洞 `bHidden` 墙体消隐与门槛过道地砖补充。 - [ ] `npm run typecheck` 0 errors。 - [ ] `npx vitest run` 全量通过。 - [ ] `npx tsx scripts/verify-packs.ts` 1671/1671 项逐字节完全匹配。
troytt added this to the [M17] 核心地下城与碰撞层原版一致性 (1.13c) milestone 2026-09-18 06:36:47 +00:00
Author
Owner

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

以 Diablo II v1.13c D2Common.dll (MD5 ee1238806ef6d6d9801d12a09d128fe1) 为黄金标准,在 maze.ts 的 stamp() 中补齐两处原版装载期行为。


1. bKillEdge 外边界接缝裁剪

原版由两处共同决定,而非单一开关:

// DrlgPreset.cpp:1105 — DRLGPRESET_InitPresetRoomGrids
if (pMazeMap->pLvlPrestTxtRecord->dwKillEdge) {
    bKillEdgeX = pDrlgRoom->nTileXPos + nTileWidth  == pDrlgCoord.nPosX + nWidth;
    bKillEdgeY = pDrlgRoom->nTileYPos + nTileHeight == pDrlgCoord.nPosY + nHeight;
}
// DrlgRoomTile.cpp:449 — DRLGROOMTILE_LoadInitRoomTiles
const int nTileCountX = pDrlgRoom->nTileWidth + (bKillEdgeX == 0);

即:只有贴合地图外缘的那一间房才裁掉多出的一格;内部房间照旧多铺一格,与邻居共享边界。

实现:

  • MazePiece 新增 killEdge(来源 LvlPrest.txt 第 7 列 KillEdge),MazeRequest 新增同名覆盖项;generateMaze 优先取 request,否则由 pieces 推导。
  • stamp() 在生效时将合成图收窄一列一行,贴边房间按 variant.width - 1 / height - 1 落图,内部接缝的一格重叠完全不变。
  • pack-act-assets.ts 的 getMazePieces 读取真实 KillEdge 列。

[!IMPORTANT]
实测 MPQ 内 LvlPrest.txt,该列按关卡族整齐二分:

  • KillEdge=1:Act 1 Cave、Act 2 Sewer、Act 2 Arcane、Act 4 Mesa、Act 5 Temple、Act 5 Ice
  • KillEdge=0:Act 1 Crypt / Barracks / Jail / Catacombs、Act 2 Basement / Tomb、Act 3 Sewer

此前引擎完全忽略该列,导致上述 6 个关卡族生成的地图恒比原版多一列一行。


2. 门洞 bHidden 消隐与门槛过道地砖补充

  • wallSurvivesLoad() 复刻 LoadInitRoomTiles 的三条过滤规则:

    • style >= 8 的 EXIT 图元直接跳过(line 466)
    • bHidden 的 DOOR 图元只生成门单位(line 485)
    • bHidden 的 EXIT 图元只登记传送点(line 500)

    三者均不输出墙体美术。

  • needsThresholdBackfill() 限定仅 bHidden 且 style <= 7 的 EXIT 触发补砖,对齐 LoadFloorWarpTiles 在 line 505 的唯一调用点。bHidden 的 DOOR 走上一分支,只生成门单位、不铺地砖。

  • backfillThresholdFloor() 按原版 gWarpTileOffsets_6FDD1320 = {0,0},{1,0},{0,1},{1,1} 在 (nX-1, nY-1) 铺 2×2 地砖并标记 hidden;供体取门洞邻近的非空地砖,等价于 DRLGROOMTILE_GetTileCache(TILETYPE_FLOOR, ...)。

  • 补砖在所有房间落图完成后统一执行:门洞位于两房交界,供体可能属于当时尚未落图的邻居。


关于 verify-packs 的影响评估

verify-packs.ts 第 125 行 if (entry.kind !== undefined && entry.kind !== 'preset') continue —— 迷宫关卡被整体跳过,只校验 preset 关卡。因此本次迷宫尺寸变化不会影响 1671 项逐字节断言,已实测确认。


验证

新增 tests/killedge-113c.test.ts(8 个用例):KillEdge 精确收窄一列一行、由 pieces 推导与 request 覆盖、内部接缝不受影响、bHidden 门体消隐、style >= 8 的 EXIT 丢弃、2×2 门槛补砖、bHidden 门不补砖。

[!NOTE]
已对新用例做变异测试:分别关闭 edgeTrim 裁剪、关闭墙体过滤,各会导致 3 个用例失败,确认断言真实有效而非空过。

门禁 结果
npm run typecheck ✅ 0 errors
npx vitest run ✅ 68 files / 1127 passed / 2 skipped
npx tsx scripts/verify-packs.ts ✅ 1671/1671 项断言通过(逐像素比对 280.2 MB)
## ✅ 已修复并合并至 main (`a15da9e`) 以 Diablo II v1.13c `D2Common.dll` (MD5 `ee1238806ef6d6d9801d12a09d128fe1`) 为黄金标准,在 `maze.ts` 的 `stamp()` 中补齐两处原版**装载期**行为。 --- ### 1. `bKillEdge` 外边界接缝裁剪 原版由两处共同决定,而非单一开关: ```c // DrlgPreset.cpp:1105 — DRLGPRESET_InitPresetRoomGrids if (pMazeMap->pLvlPrestTxtRecord->dwKillEdge) { bKillEdgeX = pDrlgRoom->nTileXPos + nTileWidth == pDrlgCoord.nPosX + nWidth; bKillEdgeY = pDrlgRoom->nTileYPos + nTileHeight == pDrlgCoord.nPosY + nHeight; } // DrlgRoomTile.cpp:449 — DRLGROOMTILE_LoadInitRoomTiles const int nTileCountX = pDrlgRoom->nTileWidth + (bKillEdgeX == 0); ``` 即:**只有贴合地图外缘的那一间房**才裁掉多出的一格;内部房间照旧多铺一格,与邻居共享边界。 实现: - `MazePiece` 新增 `killEdge`(来源 `LvlPrest.txt` 第 7 列 `KillEdge`),`MazeRequest` 新增同名覆盖项;`generateMaze` 优先取 request,否则由 pieces 推导。 - `stamp()` 在生效时将合成图收窄一列一行,贴边房间按 `variant.width - 1` / `height - 1` 落图,**内部接缝的一格重叠完全不变**。 - `pack-act-assets.ts` 的 `getMazePieces` 读取真实 `KillEdge` 列。 > [!IMPORTANT] > 实测 MPQ 内 `LvlPrest.txt`,该列按关卡族整齐二分: > - **`KillEdge=1`**:Act 1 Cave、Act 2 Sewer、Act 2 Arcane、Act 4 Mesa、Act 5 Temple、Act 5 Ice > - **`KillEdge=0`**:Act 1 Crypt / Barracks / Jail / Catacombs、Act 2 Basement / Tomb、Act 3 Sewer > > 此前引擎**完全忽略该列**,导致上述 6 个关卡族生成的地图恒比原版多一列一行。 --- ### 2. 门洞 `bHidden` 消隐与门槛过道地砖补充 - `wallSurvivesLoad()` 复刻 `LoadInitRoomTiles` 的三条过滤规则: - `style >= 8` 的 `EXIT` 图元直接跳过(line 466) - `bHidden` 的 `DOOR` 图元只生成门单位(line 485) - `bHidden` 的 `EXIT` 图元只登记传送点(line 500) 三者**均不输出墙体美术**。 - `needsThresholdBackfill()` 限定**仅** `bHidden` 且 `style <= 7` 的 `EXIT` 触发补砖,对齐 `LoadFloorWarpTiles` 在 line 505 的唯一调用点。`bHidden` 的 `DOOR` 走上一分支,只生成门单位、不铺地砖。 - `backfillThresholdFloor()` 按原版 `gWarpTileOffsets_6FDD1320 = {0,0},{1,0},{0,1},{1,1}` 在 `(nX-1, nY-1)` 铺 2×2 地砖并标记 `hidden`;供体取门洞邻近的非空地砖,等价于 `DRLGROOMTILE_GetTileCache(TILETYPE_FLOOR, ...)`。 - 补砖在**所有房间落图完成后**统一执行:门洞位于两房交界,供体可能属于当时尚未落图的邻居。 --- ### 关于 verify-packs 的影响评估 `verify-packs.ts` 第 125 行 `if (entry.kind !== undefined && entry.kind !== 'preset') continue` —— **迷宫关卡被整体跳过**,只校验 preset 关卡。因此本次迷宫尺寸变化不会影响 1671 项逐字节断言,已实测确认。 --- ### 验证 新增 `tests/killedge-113c.test.ts`(8 个用例):KillEdge 精确收窄一列一行、由 pieces 推导与 request 覆盖、内部接缝不受影响、`bHidden` 门体消隐、`style >= 8` 的 EXIT 丢弃、2×2 门槛补砖、`bHidden` 门不补砖。 > [!NOTE] > 已对新用例做**变异测试**:分别关闭 `edgeTrim` 裁剪、关闭墙体过滤,各会导致 3 个用例失败,确认断言真实有效而非空过。 | 门禁 | 结果 | |---|---| | `npm run typecheck` | ✅ 0 errors | | `npx vitest run` | ✅ 68 files / **1127 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#77
No description provided.