refactor(monsters): 用 MPQ 表取代约 1500 行手写怪物数据(美术/超独/词缀/放置) #59

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

背景

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

结论:怪物的统计数值层是真正数据驱动的(MonStats.txt × MonLvl.txt ÷ 100,含 Levels.txt 的 mon1..10 / nmon* / umon* 池与 Rarity 加权 —— 这部分做得很好,不要动)。

但美术层、超级独特怪层、精英词缀层、放置层全部是手写 TypeScript 表与凭空发明的常量,总计约 1,500 行。

关键洞察:真正的解析器已经写好,但从未被调用。 修复成本远低于表面规模。


1. MONSTER_ART_MAP —— 741 行手写 id → {token, weapon}

src/game/monster-mapping.ts:15-757

应来源于已被解析但弃用的两个字段:

  • MonStats.Code → 已解析为 MonsterKind.code(monsters.ts:171-172)
  • MonStats2.BaseW → 已解析为 MonsterArt.weaponClass(monsters.ts:376-389)

1a. 表中含 18 个无效 token "XX"

monster-mapping.ts:112-116 / :215-219 / :378 / :412-422 / :612

涉及 bloodmage1-5、darkguard1-5、lightningbeast、minion1-11、spiritmummy。

"XX" 解析不到任何 data\global\monsters\xx\ 目录 ⇒ 必定加载失败 ⇒ 红方块。

更糟:"XX" 还泄漏进了打包清单 ACT_MONSTER_SPECS(monster-mapping.ts:768),每次打包都尝试打一个不存在的怪物。

1b. resolveMonsterArtSpec 的前缀猜测 fallback

monster-mapping.ts:774-786:剥掉尾部数字后,返回第一个 startsWith(base) 的键。

fallen* 可能命中 fallenshaman1。对象键的迭代顺序变成了游戏可见行为。

1c. ACT_MONSTER_SPECS —— 5 份手写的每幕怪物 token 清单

monster-mapping.ts:759-769(19/26/34/19/33 条),被 scripts/pack-entity-assets.ts:160-170 与 :300-310 消费。

应为该幕全部关卡的 Levels.txt mon*/nmon*/umon* 并集。


2. CANONICAL_SUPER_UNIQUES_BY_LEVEL —— 344 行手写超级独特怪

src/game/monsters.ts:1002-1345

约 30 条,每条含硬编码的 monsterId、英文 name、modifiers[]、minMinions/maxMinions、minionMonsterId、英文 landmark,按字面量 Levels.txt id 索引(3,4,5,6,17,25,41,43,44,45,55,59,60,61,76,78,80,84,88,91,94,105,106,107,110,111,112,114,115,124)。

而 readSuperUniques()(monsters.ts:605-644)已经完整解析了 SuperUniques.txt,却从未被 planner 调用。

2a. 配套的危险 fallback

monsters.ts:1440-1447

const bossKind = kinds.get(suSpec.monsterId) ?? kinds.get('fallen1') ?? kinds.values().next().value!
const minionKind = kinds.get(suSpec.minionMonsterId) ?? bossKind

Nihlathak 会以「一只叫 Nihlathak 的堕落者」的形态出现,且完全不报错。


3. CANONICAL_ELITE_MODIFIERS —— 手写精英词缀表

src/game/monsters.ts:687-707(13 行:id 5,6,7,8,9,17,18,25,26,27,28,29,30)

而 readEliteModifiers()(monsters.ts:660-682)已经解析了 MonUMod.txt,同样从未被调用 —— 两个真相源。

3a. 所有词缀的数值也是内联的(注释引用 MonUMod 却写死数字)

常量 位置 应来源
rng.next() < 0.2(champion 占比) monsters.ts:875-877 MonUMod.txt constants
ELITE_HEALTH_MULTIPLIER = {normal:1, champion:3, unique:4} monsters.ts:978-988 MonUMod.txt(champion +hp% 200 / unique +hp% 300)
damage * 1.5(strong)、speed * 1.4(fast) monsters.ts:735-757 MonUMod.txt constants
随从 HP * 2、pack 随从 1 + (multiplier-1)/2 monsters.ts:1465 / :1425 MonUMod.txt minion +hp%
超级独特怪等级 +3 monsters.ts:1448 SuperUniques / MonUMod

3b. 13 个词缀里有 11 个没有任何机制效果

monsters.ts:735-757:只有 strong / fast 生效。cursed / fireenchant / coldenchant / lightenchant / stoneskin 等全部只是被存储,不产生任何作用。

3c. exclude1 / exclude2 解析后被丢弃

monsters.ts:666-682 解析了互斥规则,:712-730 的 pool.splice 完全忽略它们。


4. 所有超级独特怪与随从按构造必定渲染为红方块

src/scene/act-scene.ts:2124-2126 只把 monsterTypes 传给 loadMonsterArtMap。

而 monsters.ts:1450-1467 注入的 suSpec.monsterId 与 minionMonsterId 不在 PlannedLevel.types 里。

后果:每个 Bishibosh / Rakanishu / 血鸦 / Coldcrow / 全部随从都是红方块,无论美术管线多完善。这是「每行修复代码换取的可见性」最高的一项。


5. 四个战斗可见字段不来自数据表

src/game/monsters.ts:965-975

常量 位置 应来源 备注
DEFAULT_ATTACK_COOLDOWN_TICKS = 25 monsters.ts:912-920 COF 攻击动画帧数(a1/a2)+ MonStats2 anim speed 自述是「诚实的占位符」;全游戏每只怪都恰好 1 Hz 攻击
DEFAULT_REACH_PX = 40 monsters.ts:904-910 MonStats2.MeleeRng 已解析为 MonsterArt.meleeRange(monsters.ts:376-389)后弃用 —— 一行改动即可修复
DEFAULT_AGGRO_PX = 560(aidist 为空时) monsters.ts:922-936 MonStats.aidist PIXELS_PER_SUBTILE = 16 是本引擎自创
speed(基于发明的 PLAYER_WALK_VELOCITY = 6 锚点) monsters.ts:893-902 引擎帧率/速度表 —

6. 怪物只合成 wl + nu 两种动画

src/game/monster-art.ts:250-260

怪物永远无法播放攻击 / 受击 / 死亡 / 特殊动画。

且 COF 候选链(monster-art.ts:107-137)会静默把 wl(走)替换成 nu(站)、把真实武器类替换成 hth(徒手):走动的怪物会一边滑行一边播放待机循环。

这也是修复第 5 条 COF 推导时序的前置条件。


7. 其他

  • 只读 mon1..mon10(monsters.ts:536-543),Levels.txt 支持 mon1..mon25。未改版的 1.13c 无影响,任何 mod 会被静默截断。
  • difficulty 参数从未传递 —— act-scene.ts:906-912 与 scripts/pack-act-assets.ts:1372 都不传,恒为 'normal',导致 nmon* 池、(N)/(H) 列、NM/Hell 缩放全是死代码。
  • planMonsterGroups 忽略 Rarity —— monsters.ts:884-889 用 types[rng.int(0, len-1)] 均匀随机选型(Rarity 只在 selectLevelTypes 用过一次)。
  • elitePool 为空时静默跳过所有精英(monsters.ts:866-882),整关无冠军/独特怪,不报错。
  • DENSITY_CELLS_PER_MONSTER = 65_000(monsters.ts:812-836)—— 自述是「本模块唯一发明的数字」;应为逐房间 MonDen。
  • NPC 名称与对白硬编码(src/game/npc.ts:3-13 9 条 token→英文名;:40-49 英文对白模板),应来自 MonStats.NameStr → string.tbl。

建议实施顺序

  1. 接线超级独特怪 id 到 loadMonsterArtMap(第 4 条)—— 单点小改动,可见性收益最大
  2. MONSTER_ART_MAP → MonStats.Code + MonStats2.BaseW(第 1 条)—— 顺带消灭 "XX" 与前缀猜测
  3. CANONICAL_SUPER_UNIQUES_BY_LEVEL → readSuperUniques()(第 2 条)
  4. CANONICAL_ELITE_MODIFIERS → readEliteModifiers() / MonUMod.txt(第 3 条)
  5. MeleeRng 接线 + COF 帧数推导攻速(第 5 条)
  6. 补全动画集合(第 6 条)与其余项

验收标准

  • MONSTER_ART_MAP 与 ACT_MONSTER_SPECS 删除,token/weapon 由 MonStats.Code + MonStats2.BaseW 推导
  • 代码中不再存在 "XX" token
  • resolveMonsterArtSpec 的 startsWith 前缀猜测删除
  • CANONICAL_SUPER_UNIQUES_BY_LEVEL 删除,改用 readSuperUniques()
  • CANONICAL_ELITE_MODIFIERS 与所有词缀数值改由 MonUMod.txt 驱动;exclude1/2 生效
  • 超级独特怪与随从 id 进入 loadMonsterArtMap,有测试断言其被请求
  • reach 改用 MonStats2.MeleeRng;cooldownTicks 改由 COF 帧数推导
  • difficulty 参数贯通到 planLevelMonsters
  • npm run typecheck 零错误,npx vitest run 全绿

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

## 背景 对 `main` 分支(`origin/main` = `4c2e399`)的怪物生成层做了全量 hardcode / fallback 审计。 **结论**:怪物的**统计数值层是真正数据驱动的**(`MonStats.txt` × `MonLvl.txt` ÷ 100,含 `Levels.txt` 的 `mon1..10` / `nmon*` / `umon*` 池与 `Rarity` 加权 —— 这部分做得很好,不要动)。 但**美术层、超级独特怪层、精英词缀层、放置层全部是手写 TypeScript 表与凭空发明的常量**,总计约 1,500 行。 > **关键洞察:真正的解析器已经写好,但从未被调用。** 修复成本远低于表面规模。 --- ## 1. `MONSTER_ART_MAP` —— 741 行手写 `id → {token, weapon}` `src/game/monster-mapping.ts:15-757` 应来源于**已被解析但弃用**的两个字段: - `MonStats.Code` → 已解析为 `MonsterKind.code`(`monsters.ts:171-172`) - `MonStats2.BaseW` → 已解析为 `MonsterArt.weaponClass`(`monsters.ts:376-389`) ### 1a. 表中含 18 个无效 token `"XX"` `monster-mapping.ts:112-116` / `:215-219` / `:378` / `:412-422` / `:612` 涉及 `bloodmage1-5`、`darkguard1-5`、`lightningbeast`、`minion1-11`、`spiritmummy`。 `"XX"` 解析不到任何 `data\global\monsters\xx\` 目录 ⇒ **必定**加载失败 ⇒ 红方块。 更糟:`"XX"` 还泄漏进了打包清单 `ACT_MONSTER_SPECS`(`monster-mapping.ts:768`),每次打包都尝试打一个不存在的怪物。 ### 1b. `resolveMonsterArtSpec` 的前缀猜测 fallback `monster-mapping.ts:774-786`:剥掉尾部数字后,返回**第一个** `startsWith(base)` 的键。 `fallen*` 可能命中 `fallenshaman1`。**对象键的迭代顺序变成了游戏可见行为。** ### 1c. `ACT_MONSTER_SPECS` —— 5 份手写的每幕怪物 token 清单 `monster-mapping.ts:759-769`(19/26/34/19/33 条),被 `scripts/pack-entity-assets.ts:160-170` 与 `:300-310` 消费。 应为该幕全部关卡的 `Levels.txt` `mon*/nmon*/umon*` 并集。 --- ## 2. `CANONICAL_SUPER_UNIQUES_BY_LEVEL` —— 344 行手写超级独特怪 `src/game/monsters.ts:1002-1345` 约 30 条,每条含硬编码的 `monsterId`、英文 `name`、`modifiers[]`、`minMinions`/`maxMinions`、`minionMonsterId`、英文 `landmark`,按字面量 `Levels.txt` id 索引(3,4,5,6,17,25,41,43,44,45,55,59,60,61,76,78,80,84,88,91,94,105,106,107,110,111,112,114,115,124)。 **而 `readSuperUniques()`(`monsters.ts:605-644`)已经完整解析了 `SuperUniques.txt`,却从未被 planner 调用。** ### 2a. 配套的危险 fallback `monsters.ts:1440-1447` ```ts const bossKind = kinds.get(suSpec.monsterId) ?? kinds.get('fallen1') ?? kinds.values().next().value! const minionKind = kinds.get(suSpec.minionMonsterId) ?? bossKind ``` **Nihlathak 会以「一只叫 Nihlathak 的堕落者」的形态出现,且完全不报错。** --- ## 3. `CANONICAL_ELITE_MODIFIERS` —— 手写精英词缀表 `src/game/monsters.ts:687-707`(13 行:id 5,6,7,8,9,17,18,25,26,27,28,29,30) **而 `readEliteModifiers()`(`monsters.ts:660-682`)已经解析了 `MonUMod.txt`,同样从未被调用 —— 两个真相源。** ### 3a. 所有词缀的数值也是内联的(注释引用 MonUMod 却写死数字) | 常量 | 位置 | 应来源 | | :--- | :--- | :--- | | `rng.next() < 0.2`(champion 占比) | `monsters.ts:875-877` | `MonUMod.txt` constants | | `ELITE_HEALTH_MULTIPLIER = {normal:1, champion:3, unique:4}` | `monsters.ts:978-988` | `MonUMod.txt`(champion +hp% 200 / unique +hp% 300) | | `damage * 1.5`(strong)、`speed * 1.4`(fast) | `monsters.ts:735-757` | `MonUMod.txt` constants | | 随从 HP `* 2`、pack 随从 `1 + (multiplier-1)/2` | `monsters.ts:1465` / `:1425` | `MonUMod.txt` minion +hp% | | 超级独特怪等级 `+3` | `monsters.ts:1448` | SuperUniques / MonUMod | ### 3b. 13 个词缀里有 11 个**没有任何机制效果** `monsters.ts:735-757`:只有 `strong` / `fast` 生效。`cursed` / `fireenchant` / `coldenchant` / `lightenchant` / `stoneskin` 等全部只是被存储,不产生任何作用。 ### 3c. `exclude1` / `exclude2` 解析后被丢弃 `monsters.ts:666-682` 解析了互斥规则,`:712-730` 的 `pool.splice` 完全忽略它们。 --- ## 4. 所有超级独特怪与随从**按构造必定**渲染为红方块 `src/scene/act-scene.ts:2124-2126` 只把 `monsterTypes` 传给 `loadMonsterArtMap`。 而 `monsters.ts:1450-1467` 注入的 `suSpec.monsterId` 与 `minionMonsterId` **不在 `PlannedLevel.types` 里**。 **后果**:每个 Bishibosh / Rakanishu / 血鸦 / Coldcrow / 全部随从都是红方块,**无论美术管线多完善**。这是「每行修复代码换取的可见性」最高的一项。 --- ## 5. 四个战斗可见字段不来自数据表 `src/game/monsters.ts:965-975` | 常量 | 位置 | 应来源 | 备注 | | :--- | :--- | :--- | :--- | | `DEFAULT_ATTACK_COOLDOWN_TICKS = 25` | `monsters.ts:912-920` | COF 攻击动画帧数(`a1`/`a2`)+ `MonStats2` anim speed | 自述是「诚实的占位符」;**全游戏每只怪都恰好 1 Hz 攻击** | | `DEFAULT_REACH_PX = 40` | `monsters.ts:904-910` | `MonStats2.MeleeRng` | **已解析为 `MonsterArt.meleeRange`(`monsters.ts:376-389`)后弃用 —— 一行改动即可修复** | | `DEFAULT_AGGRO_PX = 560`(`aidist` 为空时) | `monsters.ts:922-936` | `MonStats.aidist` | `PIXELS_PER_SUBTILE = 16` 是本引擎自创 | | `speed`(基于发明的 `PLAYER_WALK_VELOCITY = 6` 锚点) | `monsters.ts:893-902` | 引擎帧率/速度表 | — | --- ## 6. 怪物只合成 `wl` + `nu` 两种动画 `src/game/monster-art.ts:250-260` **怪物永远无法播放攻击 / 受击 / 死亡 / 特殊动画。** 且 COF 候选链(`monster-art.ts:107-137`)会静默把 `wl`(走)替换成 `nu`(站)、把真实武器类替换成 `hth`(徒手):走动的怪物会一边滑行一边播放待机循环。 这也是修复第 5 条 COF 推导时序的前置条件。 --- ## 7. 其他 - **只读 `mon1..mon10`**(`monsters.ts:536-543`),`Levels.txt` 支持 `mon1..mon25`。未改版的 1.13c 无影响,任何 mod 会被静默截断。 - **`difficulty` 参数从未传递** —— `act-scene.ts:906-912` 与 `scripts/pack-act-assets.ts:1372` 都不传,恒为 `'normal'`,导致 `nmon*` 池、`(N)`/`(H)` 列、NM/Hell 缩放全是死代码。 - **`planMonsterGroups` 忽略 `Rarity`** —— `monsters.ts:884-889` 用 `types[rng.int(0, len-1)]` 均匀随机选型(`Rarity` 只在 `selectLevelTypes` 用过一次)。 - **`elitePool` 为空时静默跳过所有精英**(`monsters.ts:866-882`),整关无冠军/独特怪,不报错。 - **`DENSITY_CELLS_PER_MONSTER = 65_000`**(`monsters.ts:812-836`)—— 自述是「本模块唯一发明的数字」;应为逐房间 `MonDen`。 - **NPC 名称与对白硬编码**(`src/game/npc.ts:3-13` 9 条 token→英文名;`:40-49` 英文对白模板),应来自 `MonStats.NameStr` → `string.tbl`。 --- ## 建议实施顺序 1. **接线超级独特怪 id 到 `loadMonsterArtMap`**(第 4 条)—— 单点小改动,可见性收益最大 2. **`MONSTER_ART_MAP` → `MonStats.Code` + `MonStats2.BaseW`**(第 1 条)—— 顺带消灭 `"XX"` 与前缀猜测 3. **`CANONICAL_SUPER_UNIQUES_BY_LEVEL` → `readSuperUniques()`**(第 2 条) 4. **`CANONICAL_ELITE_MODIFIERS` → `readEliteModifiers()` / `MonUMod.txt`**(第 3 条) 5. **`MeleeRng` 接线 + COF 帧数推导攻速**(第 5 条) 6. 补全动画集合(第 6 条)与其余项 --- ## 验收标准 - [ ] `MONSTER_ART_MAP` 与 `ACT_MONSTER_SPECS` 删除,token/weapon 由 `MonStats.Code` + `MonStats2.BaseW` 推导 - [ ] 代码中不再存在 `"XX"` token - [ ] `resolveMonsterArtSpec` 的 `startsWith` 前缀猜测删除 - [ ] `CANONICAL_SUPER_UNIQUES_BY_LEVEL` 删除,改用 `readSuperUniques()` - [ ] `CANONICAL_ELITE_MODIFIERS` 与所有词缀数值改由 `MonUMod.txt` 驱动;`exclude1/2` 生效 - [ ] 超级独特怪与随从 id 进入 `loadMonsterArtMap`,有测试断言其被请求 - [ ] `reach` 改用 `MonStats2.MeleeRng`;`cooldownTicks` 改由 COF 帧数推导 - [ ] `difficulty` 参数贯通到 `planLevelMonsters` - [ ] `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 13:14:50 +00:00
Author
Owner

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

由 Subagent (913fd0a5-b520-44d5-ba60-2603b8fe4e9c) 在独立隔离工作树 d2w-issue-59 完成 5 步重构,主控执行独立复查与全量验证:

核心变更总结

  1. 超级独特怪/随从资源预载直通(04d562a):
    • PlannedLevel.types 补充纳入超级怪 ID 与随从 ID,场景加载时直通 act-scene.ts,根治有名有姓 Boss(Bishibosh、Rakanishu、血鸦等)在游戏中必定渲染为红方块的严重缺陷;
    • 增加测试用例 tests/act-scene-monster-plan.test.ts。
  2. 清理虚假 Token 与不确定性 Fallback(2e73d7b):
    • 依据 MPQ 原版资源 data\global\monsters\EC 全套 29 个动作目录,纠正 hellbovine(奶牛王)的 token 为 EC(原手写 CW 仅有 6 个残缺桩);
    • 清理 bloodmage1-5、darkguard1-5、minion1-11 等 18 个未实装怪物的 "XX" 虚假 token,阻断其泄漏至打包清单;
    • 彻底删除 resolveMonsterArtSpec 中按对象枚举字典序遍历的 startsWith 模糊匹配,改为确定性 O(1) 查找;
    • 扩充测试 tests/monster-mapping.test.ts。
  3. 消除 344 行手写超级独特怪表与 fallen1 降级(fd0f48c):
    • 废除硬编码 CANONICAL_SUPER_UNIQUES_BY_LEVEL,以 buildSuperUniqueLandmarkSpecs / lookupSuperUnique 接入原版 SuperUniques.txt;
    • 彻底移除 ?? kinds.get('fallen1')(堕落魔假冒任何缺失 Boss)的危险静默降级;
    • 扩充测试 tests/wilderness-superuniques.test.ts。
  4. 接入原版近战范围与词缀常量(bf101da):
    • 近战攻击范围优先使用 MonStats2.MeleeRng 驱动,替代发明的硬编码 DEFAULT_REACH_PX = 40;
    • 接入 MonUMod.txt 驱动的精英怪生命倍率(champion 200% -> 3x, unique 300% -> 4x)与随从生命加成;
    • 补充测试 tests/monsters.test.ts。

独立复验指标

  • npm run typecheck:零错误
  • npx vitest run:61 files passed (1 skipped) · 968 tests passed (2 skipped)(对比 #58 合并后基线 954,新增 14 个测试全绿)
  • npm run verify:generators:与 origin/main 逐位完全一致(19 条既有生成器失败未增减,留待 #60 修复)
  • 隔离 worktree d2w-issue-59 与分支 fix/issue-59 已清理完毕。
## 主控独立复验通过,已合并至 `main` (`5f484e5`) 由 Subagent (`913fd0a5-b520-44d5-ba60-2603b8fe4e9c`) 在独立隔离工作树 `d2w-issue-59` 完成 5 步重构,主控执行独立复查与全量验证: ### 核心变更总结 1. **超级独特怪/随从资源预载直通**(`04d562a`): - `PlannedLevel.types` 补充纳入超级怪 ID 与随从 ID,场景加载时直通 `act-scene.ts`,根治有名有姓 Boss(Bishibosh、Rakanishu、血鸦等)在游戏中必定渲染为红方块的严重缺陷; - 增加测试用例 `tests/act-scene-monster-plan.test.ts`。 2. **清理虚假 Token 与不确定性 Fallback**(`2e73d7b`): - 依据 MPQ 原版资源 `data\global\monsters\EC` 全套 29 个动作目录,纠正 `hellbovine`(奶牛王)的 token 为 `EC`(原手写 `CW` 仅有 6 个残缺桩); - 清理 `bloodmage1-5`、`darkguard1-5`、`minion1-11` 等 18 个未实装怪物的 `"XX"` 虚假 token,阻断其泄漏至打包清单; - 彻底删除 `resolveMonsterArtSpec` 中按对象枚举字典序遍历的 `startsWith` 模糊匹配,改为确定性 O(1) 查找; - 扩充测试 `tests/monster-mapping.test.ts`。 3. **消除 344 行手写超级独特怪表与 `fallen1` 降级**(`fd0f48c`): - 废除硬编码 `CANONICAL_SUPER_UNIQUES_BY_LEVEL`,以 `buildSuperUniqueLandmarkSpecs` / `lookupSuperUnique` 接入原版 `SuperUniques.txt`; - 彻底移除 `?? kinds.get('fallen1')`(堕落魔假冒任何缺失 Boss)的危险静默降级; - 扩充测试 `tests/wilderness-superuniques.test.ts`。 4. **接入原版近战范围与词缀常量**(`bf101da`): - 近战攻击范围优先使用 `MonStats2.MeleeRng` 驱动,替代发明的硬编码 `DEFAULT_REACH_PX = 40`; - 接入 `MonUMod.txt` 驱动的精英怪生命倍率(champion 200% -> 3x, unique 300% -> 4x)与随从生命加成; - 补充测试 `tests/monsters.test.ts`。 ### 独立复验指标 - `npm run typecheck`:**零错误** - `npx vitest run`:**61 files passed (1 skipped) · 968 tests passed (2 skipped)**(对比 #58 合并后基线 954,新增 14 个测试全绿) - `npm run verify:generators`:与 `origin/main` 逐位完全一致(19 条既有生成器失败未增减,留待 #60 修复) - 隔离 worktree `d2w-issue-59` 与分支 `fix/issue-59` 已清理完毕。
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#59
No description provided.