refactor(map): 用 LvlPrest/Levels/Objects 表取代约 1100 行手写地图数据 #61
Labels
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: troytt/diablo2-web#61
Loading…
Reference in New Issue
No description provided.
Delete Branch "%!s(<nil>)"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
背景
对
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-272830 个关卡 × 最多 13 个预设名字字符串,规定每个野外关卡贴哪些
LvlPrest行。应来源于:
LvlPrest.txt按LevelId/LevelNamejoin,以及 D2MOO 的DRLGOUTWILD_SpawnSpecialPresetsid 表。并且存在第二份分歧副本:
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.txtid:[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-790singleRoom硬编码预设名字('Act 2 - Tomb Tainted Sun X'、'Act 5 - Ice River A/B'、'Act 5 - Ice Pool A/B')绑定字面量关卡 idmaze.ts:736-758maze.ts:807-824maze.ts:850MAZE_SHARED_PRESET_NAMES = ['Act 4 - Lava X']maze.ts:388-408MAZE_PIECE_FAMILIES用名字前缀映射LvlTypes→LvlPrest,真正的 join key 是LvlPrest.LevelId→Levels.LevelTypemaze.ts:357-385KIND_PREFIXES25 个角色词,靠英文子串分类预设('Coldcrow'、'Pitspawn'、'Leatherarm'、'Kaa'、'Tight Spot'…)maze-special-passes.ts全篇LvlPrest名字('Act 2 - Lair Tight Spot S'、'Act 3 - Mephisto Complex'、'Act 5 - ThroneRoom'…)+ 约 45 个字面关卡 id3.
maze-objects.ts的 5 张物件表src/game/maze-objects.ts:130-223CANONICAL_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容器簇0.4/0.8、0.3/0.7、chance(0.5):308-312)orths.length <= 2(:604-606���—— 实际等于「所有房间」,「仅走廊」规则形同虚设4.
ACT_POPULATION_CONFIG—— 约 60 个物件 idsrc/game/wilderness.ts:3708-3744每幕的
waypointId/shrinePool[]/wellId/chestPool[]/scatterPool[]。应来源于:
Objects.txt+Levels.txtObjGrp0..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-368018 个关卡 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 自认:
应来源于:
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-3004Levels.txt SizeX/SizeY == -1时应从所接的路障关卡推导CHAOS_SANCTUARY_LEVEL_ID = 108、SIZE_X/Y = 120preset.ts:18-24Levels.txt关卡 108 的SizeX/SizeYCHAOS_SANCTUARY_PLACEMENTS7 条手打坐标preset.ts:109-117LvlPrest.txt第四幕行 + DRLG 预设装配 passx: 0/16/48, y: 0/32wilderness.ts:3016-3023LvlPrest SizeX/SizeYACT5_SIEGE_STRIP_NAMES15 条名字wilderness-siege.ts:22-38LvlPrest.txt的Act 5 - Siege行y < 6 || y > 42(出现 5 次)wilderness-siege.ts:114/136/172/229/276canvas.heighti * 16strip 步长wilderness-siege.ts:374-387ds1.widthCANONICAL_GROUND_TILES/CANONICAL_GROUND_POOLSwilderness.ts:332-353rarityFrameIndex加权掷骰;每个 pool 只有一条目导致多 DT1 混合机制全是死代码WILDERNESS_DT1_MASK8 条wilderness.ts:135-144LvlTypes.txtId 索引;任何改名/mod 静默得到 mask 0wilderness.ts:1174-1206、:1262-1295LvlPrestId」配对表wilderness.ts:3370-3377等 4 组、:3398-3402等 4 组LvlTypes.txt File1..32actOfLevelid 区间1-39/40-74/75-102/103-108/109-136wilderness.ts:3686-3698Levels.txt的Act列41-46, 79-82, 111/112/117, 2-7wilderness.ts:3589-3666/slums/ /burbs/ /metro/ /snow/wilderness.ts:5089-5104LvlPrest.txt的LevelId列(每个预设已声明自己属于哪关)8.
LvlSub已解析但被 fallback 架空wilderness.ts:2342-2345/:2440/:2446row.max[themeIndex] ?? 0等 ⇒ 表少一列Prob4时整类景物(树、石头)静默消失wilderness.ts:2443-2454BordType 0的「整关仅一个」上限因min(1, Infinity)恒等于 1,实际是「每行一个」,预算从未起约束作用wilderness.ts:4003-4005sub.max[subTheme] ?? sub.max[0] ?? 0⇒ 退回发明的rng.int(1,2)wilderness.ts:2349-23549. 重复常量定义
SUB_TILES_PER_TILE定义了 4 次:map.ts(规范)、maze-objects.ts:24、level-links.ts:39等。应统一 import。建议实施顺序
LEVEL_NAMES_ZH→CHI/string.tbl(第 6 条)—— 最独立、风险最低、已有 TODO 与解码器OUTDOOR_WAYPOINT_LEVELS→Levels.txt Waypoint != 255(第 5 条)—— 单表单列actOfLevel/ 区段派发 →Levels.txt Act(第 7 条)—— 消灭三份重复Objects.txt(第 3、4 条)LvlPrest.txt(第 1、2 条)—— 规模最大,需先建立 join 基础设施Levels.txt/ DT1 库(第 7 条其余)LvlSubfallback 架空(第 8 条)验收标准
LEVEL_NAMES_ZH删除,改由CHI/string.tbl解码;无 MPQ 时明确降级到英文而非静默混用OUTDOOR_WAYPOINT_LEVELS删除,改读Levels.txt WaypointactOfLevel与所有 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.txtjoinMAZE_PIECE_FAMILIES/KIND_PREFIXES的名字前缀匹配改为LvlPrest.LevelIdjoinLevels.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
父追踪 Issue:#64(完整审计报告与修复路线图)
主控独立复验通过,已合并至
main(710efa0)由 Subagent (
d6564505-4e04-48d8-b5b3-077f9bb8198c) 在独立隔离工作树d2w-issue-61完成 4 步全量重构,主控执行独立复查与全量验证:核心变更总结
8ee3a4c):SUB_TILES_PER_TILE在全库 4 处的重复定义,集中由src/game/map.ts作为单一权威真相源导出;src/game/wilderness-siege.ts中彻底消除 5 处写死的y < 6 || y > 42与固定 16 瓦片步长假设,重构为基于实际画布高度与 DS1 几何(含重叠边缘处理)的动态步长。string.tbl动态解码(7efdad4):src/game/level-names-zh.ts实现decodeLevelNamesFromTbl与CANONICAL_LEVEL_NAME_KEYS,动态解析 Blizzardstring.tbl;sceneNameZh多级回退链路:覆盖名 ->string.tbl解码 -> 静态表兜底 -> 英文原名,在沙箱与无 MPQ 环境保持稳健;tests/tbl.test.ts新增 5 个专项测试。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 个测试。Objects.txt映射校验及LvlPrest族系推导(a7a3ce1):resolveObjectsTxtId、validateDungeonObjectPools与validateActPopulationConfig,为 223 个地牢对象与 82 个野外对象与 BlizzardObjects.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 索引数据,全部一致)d2w-issue-61与分支fix/issue-61已清理完毕。