[P0][D01] 内嵌 MonStats.txt 掉落列并填充 monsterKinds(修复全游戏回落 Act 1 H2H A) #410

Closed
opened 2026-09-24 03:25:57 +00:00 by troytt · 1 comment
Owner

现状(最高优先级缺陷)

src/game/embedded-drop-tables.ts:108 硬编码:

monsterKinds: new Map(),
superUniques: new Map(),

而 src/data/canonical-drop-data.ts 只导出了 14 张表(RAW_TC_EX / RAW_WEAPONS / RAW_ARMOR / RAW_MISC / RAW_ITEM_RATIO / RAW_ITEM_TYPES / RAW_UNIQUE_ITEMS / RAW_SET_ITEMS / RAW_SETS / RAW_MAGIC_PREFIX / RAW_MAGIC_SUFFIX / RAW_RARE_PREFIX / RAW_RARE_SUFFIX),没有 RAW_MONSTATS。

实机路径是 src/scene/act-scene.ts:5011 → getEmbeddedDropTables() → engine.setDropTables()。
于是 src/game/engine.ts:864 的 dropTables.monsterKinds.get(...) 永远返回 undefined,
流程必然落到 src/game/engine.ts:870 的 tcName = 'Act 1 H2H A'。

后果:从血鸦到巴尔,五幕 × 三难度的每一只怪物,都在用「第一幕普通近战 A」这一张掉落表。
drop-pipeline.ts / treasure-class.ts / treasure-engine.ts 里已经写好的 TC 树、难度分层、
Champion/Unique 提升链,在实机中一行都没有被真正使用。

1.13c 真实行为

MonStats.txt 为每个怪物类型提供 12 个掉落列:

列 含义
TreasureClass1 / TreasureClass1(N) / TreasureClass1(H) 普通怪,Normal / Nightmare / Hell
TreasureClass2 / (N) / (H) Champion
TreasureClass3 / (N) / (H) Unique(含 Minion)
TreasureClass4 / (N) / (H) Quest / Boss

readMonsterKinds()(drop-pipeline.ts:241)与 getMonsterTreasureClass()(monsters.ts:319-332)
已经实现了正确的列选择逻辑——唯独没有数据喂进去。

验收标准

  • 从 /usr/local/google/home/taodao/d2-data/Patch_D2.mpq 抽取 data\global\excel\MonStats.txt,
    裁剪出 Id / Level / Level(N) / Level(H) / boss / TreasureClass1..4 × 3 难度 等掉落必需列,
    以与其余 13 张表一致的方式内嵌为 RAW_MONSTATS。
  • getEmbeddedDropTables() 用 readMonsterKinds(parseTable(...)) 填充 monsterKinds,删除 new Map()。
  • 内嵌裁剪由离线脚本生成(对齐项目「离线打包脚本与运行时共享同一数据契约」规则),脚本纳入 scripts/。
  • 新增 vitest:断言 monsterKinds.size 等于 MonStats.txt 中有效怪物行数,且对
    skeleton1 / fallen1 / bloodraven / diablo / baalcrab 等抽样怪物,
    三难度 × 四 monsterType 解析出的 TC 名与 1.13c 原表逐字符一致。
  • 回归断言:实机随机击杀 100 只不同 monsterKind 的怪,命中 'Act 1 H2H A' 的次数
    只应来自真正配置了该 TC 的怪物,而非兜底。

依赖

本 issue 是整个 M18 的前置,D04 / D07 / D09 / D19 全部依赖它。

## 现状(最高优先级缺陷) `src/game/embedded-drop-tables.ts:108` 硬编码: ```ts monsterKinds: new Map(), superUniques: new Map(), ``` 而 `src/data/canonical-drop-data.ts` 只导出了 14 张表(`RAW_TC_EX` / `RAW_WEAPONS` / `RAW_ARMOR` / `RAW_MISC` / `RAW_ITEM_RATIO` / `RAW_ITEM_TYPES` / `RAW_UNIQUE_ITEMS` / `RAW_SET_ITEMS` / `RAW_SETS` / `RAW_MAGIC_PREFIX` / `RAW_MAGIC_SUFFIX` / `RAW_RARE_PREFIX` / `RAW_RARE_SUFFIX`),**没有 `RAW_MONSTATS`**。 实机路径是 `src/scene/act-scene.ts:5011` → `getEmbeddedDropTables()` → `engine.setDropTables()`。 于是 `src/game/engine.ts:864` 的 `dropTables.monsterKinds.get(...)` **永远返回 undefined**, 流程必然落到 `src/game/engine.ts:870` 的 `tcName = 'Act 1 H2H A'`。 **后果:从血鸦到巴尔,五幕 × 三难度的每一只怪物,都在用「第一幕普通近战 A」这一张掉落表。** `drop-pipeline.ts` / `treasure-class.ts` / `treasure-engine.ts` 里已经写好的 TC 树、难度分层、 Champion/Unique 提升链,在实机中一行都没有被真正使用。 ## 1.13c 真实行为 `MonStats.txt` 为每个怪物类型提供 12 个掉落列: | 列 | 含义 | |---|---| | `TreasureClass1` / `TreasureClass1(N)` / `TreasureClass1(H)` | 普通怪,Normal / Nightmare / Hell | | `TreasureClass2` / `(N)` / `(H)` | Champion | | `TreasureClass3` / `(N)` / `(H)` | Unique(含 Minion) | | `TreasureClass4` / `(N)` / `(H)` | Quest / Boss | `readMonsterKinds()`(`drop-pipeline.ts:241`)与 `getMonsterTreasureClass()`(`monsters.ts:319-332`) 已经实现了正确的列选择逻辑——**唯独没有数据喂进去**。 ## 验收标准 - [ ] 从 `/usr/local/google/home/taodao/d2-data/Patch_D2.mpq` 抽取 `data\global\excel\MonStats.txt`, 裁剪出 `Id` / `Level` / `Level(N)` / `Level(H)` / `boss` / `TreasureClass1..4 × 3 难度` 等掉落必需列, 以与其余 13 张表一致的方式内嵌为 `RAW_MONSTATS`。 - [ ] `getEmbeddedDropTables()` 用 `readMonsterKinds(parseTable(...))` 填充 `monsterKinds`,删除 `new Map()`。 - [ ] 内嵌裁剪由离线脚本生成(对齐项目「离线打包脚本与运行时共享同一数据契约」规则),脚本纳入 `scripts/`。 - [ ] 新增 vitest:断言 `monsterKinds.size` 等于 MonStats.txt 中有效怪物行数,且对 `skeleton1` / `fallen1` / `bloodraven` / `diablo` / `baalcrab` 等抽样怪物, 三难度 × 四 monsterType 解析出的 TC 名与 1.13c 原表逐字符一致。 - [ ] 回归断言:实机随机击杀 100 只不同 `monsterKind` 的怪,命中 `'Act 1 H2H A'` 的次数 **只应来自真正配置了该 TC 的怪物**,而非兜底。 ## 依赖 本 issue 是整个 M18 的前置,D04 / D07 / D09 / D19 全部依赖它。
troytt added this to the [M18] 打怪掉落物品:怪物死亡掉落全链路 1.13c 还原 milestone 2026-09-24 03:25:57 +00:00
Author
Owner

Resolution Summary (Issue #410)

  • MonStats Drop Columns Embedded: Extracted 19 essential drop & level columns from 1.13c MonStats.txt and embedded in src/data/canonical-drop-data.ts as RAW_MONSTATS.
  • Runtime monsterKinds Hydration: src/game/embedded-drop-tables.ts hydrates all 734 unique monster kinds from RAW_MONSTATS using readMonsterKinds().
  • Offline Extraction Script: Added scripts/pack-canonical-drop-data.ts to enforce offline packing and runtime contract parity against official 1.13c MPQ data (Patch_D2.mpq, d2exp.mpq, d2data.mpq).
  • Verification: tests/monster-treasure-class.test.ts passes 19/19 tests (100%), verifying exact 1.13c TC resolution across all 3 difficulties with zero fallbacks.
### Resolution Summary (Issue #410) - **MonStats Drop Columns Embedded**: Extracted 19 essential drop & level columns from 1.13c `MonStats.txt` and embedded in `src/data/canonical-drop-data.ts` as `RAW_MONSTATS`. - **Runtime monsterKinds Hydration**: `src/game/embedded-drop-tables.ts` hydrates all 734 unique monster kinds from `RAW_MONSTATS` using `readMonsterKinds()`. - **Offline Extraction Script**: Added `scripts/pack-canonical-drop-data.ts` to enforce offline packing and runtime contract parity against official 1.13c MPQ data (`Patch_D2.mpq`, `d2exp.mpq`, `d2data.mpq`). - **Verification**: `tests/monster-treasure-class.test.ts` passes 19/19 tests (100%), verifying exact 1.13c TC resolution across all 3 difficulties with zero fallbacks.
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#410
No description provided.