# RogueConfig 重构设计:从纯随机到规则化随机组合 - **创建日期**: 2026-07-05 - **状态**: 已批准(待实施) - **作用域**: 重构 `assets/script/game/map/RogueConfig.ts` 及配套调用方 - **目标**: 把肉鸽刷怪系统从「每只怪独立随机」改造为「在硬编码规则内的随机组合变动」 --- ## 1. 背景与问题 当前 [RogueConfig.ts](../../../assets/script/game/map/RogueConfig.ts) 的 `RogueSpawningEngine.generateWave()` 存在以下问题: 1. **纯随机生成**:每只怪独立从 6 种普通类型中 `Math.random()` 抽取,缺乏组合设计感。 2. **波次无节奏**:5 波之后纯粹 6-12 随机数,难度曲线只靠数量与 `growthRatePerWave` 撑场。 3. **强化系统空转**:`AffixType` 枚举定义 11 种词缀,但生成时 `affixes: []` 始终为空。 4. **可配置性差**:策划无法通过修改配置数据调整某一波的怪物组合与强化。 ## 2. 重构目标 1. **新增多个怪物组合配置**:以「小队(Squad)」为单位硬编码多种组合模板。 2. **硬编码多个强化配置**:分为「波次级环境强化(WaveEnchant)」+「个体精英强化(MonsterElite)」双层。 3. **硬编码每波次配置**:每波包含 `基础怪物数 + 可选小队池 + 可选强化池` 三字段,组合方式可控。 4. **保持接口兼容**:调用方 [MissionMonComp.ts](../../../assets/script/game/map/MissionMonComp.ts) 改动最小化。 ## 3. 设计决策 | 决策点 | 选择 | 理由 | |---|---|---| | 组合颗粒度 | **小队模块化**(3-4 只怪/小队) | 每波拼装 2-4 个小队,变化丰富且可控 | | 强化层级 | **双层混合**(波次级 + 个体级) | 既保留波次"环境"主题感,又有单体惊喜 | | 波次配置组织 | **按波次逐条硬编码** | 每波字段完整独立,调参最直接 | | 硬编码范围 | **wave 1~30 + 超出复用 wave 30** | 长线体验,超出后由全局乘子维持难度增长 | | 个体强化种类 | **精选 5 种**(Elite/Berserk/Shield/Swift/Regen) | 数量适中,调参简单 | | Boss 节奏 | **每 5 波一个 Boss**(5/10/15/20/25/30) | 沿用现有仪式感 | | 接口策略 | **同时重构接口** | 数据更干净,调用方改动可控 | | 难度缩放 | **叠加波次级 HP/AP 乘子**(每 5 波 +15% HP / +8% AP) | 与数量、强化叠加形成明显难度曲线 | ## 4. 整体架构与数据流 ``` WaveConfig (硬编码 30 波) ├─ base_count 基础怪物数 ├─ squad_pool 小队池 (引用 SquadConfig.id) └─ enchant_pool 波次强化池 (引用 WaveEnchant.id) │ ▼ ┌───────────────────────────────────────────┐ │ RogueSpawningEngine.generateWave(n) │ │ 1. 取 WaveConfig │ │ 2. 计算全局 hp/ap 乘子 (波次递进 + Enchant) │ │ 3. 按权重从 squad_pool 抽小队拼装到 base_count │ │ 4. Boss 波首位放 Boss │ │ 5. 按 enchant_pool 抽 0~2 个 WaveEnchant │ │ 6. 每只小怪按概率抽个体 MonsterElite │ └───────────────────────────────────────────┘ │ ▼ GeneratedMonster[] (扩展接口) │ ▼ MissionMonComp.addMonsterAtGrid (读取 hp/ap 应用到 HeroAttrsComp) ``` **核心约束**: - 单波总怪数 ≤ 12(网格上限,3 行 4 列) - Boss 占首位 - 超过 wave 30 用第 30 波配置 + 持续递增的全局乘子 ## 5. 数据结构定义 ### 5.1 个体强化(MonsterElite) > **HeroAttrsComp 字段适配发现**:经检查 [HeroAttrsComp.ts](../../../assets/script/game/hero/HeroAttrsComp.ts) 无 `atk_cd` / `regen_per_sec` 字段。本次先不改造 HeroAttrsComp,Swift/Regen 简化为「数值差异化」,cd_mul 与 regen 作为后续扩展点留口子。 ```typescript export enum MonsterElite { Elite = 0, // 精英:全面强化 Berserk = 1, // 狂暴:高攻低血 Shield = 2, // 护盾:坦克 Swift = 4, // 疾风:平衡偏输出 Regen = 3, // 再生:高血偏弱攻 } export const MonsterEliteSet: Record = { [MonsterElite.Elite]: { name: "精英", hp_mul: 1.5, ap_mul: 1.3 }, [MonsterElite.Berserk]: { name: "狂暴", hp_mul: 0.9, ap_mul: 1.6 }, [MonsterElite.Shield]: { name: "护盾", hp_mul: 1.8, ap_mul: 0.8 }, [MonsterElite.Swift]: { name: "疾风", hp_mul: 1.0, ap_mul: 1.15 }, [MonsterElite.Regen]: { name: "再生", hp_mul: 1.3, ap_mul: 0.95 }, }; ``` ### 5.2 波次级强化(WaveEnchant) ```typescript export interface WaveEnchant { id: string; name: string; desc: string; hp_mul?: number; // 全员 HP 乘子 ap_mul?: number; // 全员 AP 乘子 cd_mul?: number; // 全员攻击间隔乘子(后续扩展) elite_rate_mul?: number; // 该波个体强化出现概率乘子 weight: number; // 在强化池中的抽取权重 } export const WaveEnchantLibrary: Record = { frenzy: { id: "frenzy", name: "狂热浪潮", desc: "全员攻击力+25%", ap_mul: 1.25, weight: 10 }, ironhide: { id: "ironhide", name: "铁皮大军", desc: "全员生命+40%", hp_mul: 1.40, weight: 10 }, swift: { id: "swift", name: "疾风突袭", desc: "全员攻速+20%", cd_mul: 0.80, weight: 8 }, nightmare: { id: "nightmare", name: "梦魇来袭", desc: "个体强化率×2,HP+15%", elite_rate_mul: 2.0, hp_mul: 1.15, weight: 5 }, fortress: { id: "fortress", name: "钢铁堡垒", desc: "全员 HP+60% AP-15%", hp_mul: 1.60, ap_mul: 0.85, weight: 6 }, }; ``` ### 5.3 小队模板(SquadConfig) ```typescript export interface SquadSlot { type: MonType; count: number; elite_chance?: number; // 0~1,小队成员出现个体强化的概率(默认 0) elite_pool?: MonsterElite[]; // 可选的个体强化池 } export interface SquadConfig { id: string; name: string; slots: SquadSlot[]; weight: number; } export const SquadLibrary: Record = { melee_grunt: { id: "melee_grunt", name: "近战步兵组", weight: 10, slots: [{ type: MonType.Melee, count: 3 }] }, assassin_squad: { id: "assassin_squad", name: "刺客突袭组", weight: 6, slots: [{ type: MonType.Assassin, count: 2 }, { type: MonType.Support, count: 1 }] }, mixed_balanced: { id: "mixed_balanced", name: "平衡混合组", weight: 8, slots: [{ type: MonType.Melee, count: 1 }, { type: MonType.Long, count: 1 }, { type: MonType.Heavy, count: 1 }] }, long_line: { id: "long_line", name: "远程线列组", weight: 7, slots: [{ type: MonType.Long, count: 2 }, { type: MonType.Support, count: 1 }] }, heavy_shield: { id: "heavy_shield", name: "重盾堡垒组", weight: 5, slots: [{ type: MonType.Heavy, count: 1 }, { type: MonType.Melee, count: 2 }] }, summoner_cult: { id: "summoner_cult", name: "召唤教派组", weight: 4, slots: [{ type: MonType.Summoner, count: 1 }, { type: MonType.Long, count: 2 }] }, }; ``` ### 5.4 波次配置(WaveConfig) ```typescript export interface WaveConfig { base_count: number; // 该波基础怪物数 (1~12) squad_pool: string[]; // 可选小队 id 池 enchant_pool?: string[]; // 可选波次强化 id 池(可空表示无强化) boss_wave?: boolean; // 是否 Boss 波 elite_base_rate?: number; // 该波个体强化基础概率(默认按波次递增) } export const WaveConfigs: Record = { // 1-4: 教学期 1: { base_count: 3, squad_pool: ["melee_grunt"] }, 2: { base_count: 4, squad_pool: ["melee_grunt", "mixed_balanced"] }, 3: { base_count: 5, squad_pool: ["melee_grunt", "mixed_balanced", "long_line"] }, 4: { base_count: 6, squad_pool: ["mixed_balanced", "long_line", "heavy_shield"] }, // 5: 第一 Boss 5: { base_count: 7, squad_pool: ["melee_grunt", "mixed_balanced", "heavy_shield"], boss_wave: true }, // 6-9: 引入波次强化 6: { base_count: 7, squad_pool: ["assassin_squad", "long_line", "mixed_balanced"], enchant_pool: ["frenzy"], elite_base_rate: 0.10 }, 7: { base_count: 8, squad_pool: ["melee_grunt", "heavy_shield", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.12 }, 8: { base_count: 8, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.14 }, 9: { base_count: 9, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.16 }, // 10: 第二 Boss 10: { base_count: 10, squad_pool: ["melee_grunt", "assassin_squad", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.18 }, // 11-14: 组合多样化 11: { base_count: 10, squad_pool: ["assassin_squad", "long_line", "summoner_cult", "mixed_balanced"], enchant_pool: ["swift", "frenzy"], elite_base_rate: 0.18 }, 12: { base_count: 10, squad_pool: ["heavy_shield", "melee_grunt", "mixed_balanced", "long_line"], enchant_pool: ["ironhide"], elite_base_rate: 0.20 }, 13: { base_count: 11, squad_pool: ["assassin_squad", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.22 }, 14: { base_count: 11, squad_pool: ["heavy_shield", "long_line", "melee_grunt"], enchant_pool: ["ironhide", "swift"], elite_base_rate: 0.24 }, // 15: 第三 Boss(中期高潮) 15: { base_count: 11, squad_pool: ["assassin_squad", "mixed_balanced", "summoner_cult"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.25 }, // 16-24: 高压阶段 16: { base_count: 11, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "swift"], elite_base_rate: 0.26 }, 17: { base_count: 11, squad_pool: ["melee_grunt", "summoner_cult", "mixed_balanced"], enchant_pool: ["ironhide", "frenzy"], elite_base_rate: 0.27 }, 18: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "heavy_shield"], enchant_pool: ["nightmare", "swift"], elite_base_rate: 0.28 }, 19: { base_count: 12, squad_pool: ["mixed_balanced", "summoner_cult", "melee_grunt"], enchant_pool: ["fortress"], elite_base_rate: 0.28 }, 20: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "long_line"], enchant_pool: ["frenzy", "ironhide"], boss_wave: true, elite_base_rate: 0.30 }, 21: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "summoner_cult"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.30 }, 22: { base_count: 12, squad_pool: ["heavy_shield", "long_line", "mixed_balanced"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.30 }, 23: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "melee_grunt"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.32 }, 24: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "long_line"], enchant_pool: ["fortress", "swift"], elite_base_rate: 0.32 }, // 25: 第四 Boss 25: { base_count: 12, squad_pool: ["assassin_squad", "summoner_cult", "heavy_shield"], enchant_pool: ["nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.35 }, // 26-29: 终极阶段 26: { base_count: 12, squad_pool: ["melee_grunt", "assassin_squad", "long_line", "summoner_cult"], enchant_pool: ["frenzy", "nightmare"], elite_base_rate: 0.35 }, 27: { base_count: 12, squad_pool: ["heavy_shield", "mixed_balanced", "summoner_cult"], enchant_pool: ["ironhide", "fortress"], elite_base_rate: 0.38 }, 28: { base_count: 12, squad_pool: ["assassin_squad", "long_line", "melee_grunt"], enchant_pool: ["swift", "nightmare"], elite_base_rate: 0.40 }, 29: { base_count: 12, squad_pool: ["mixed_balanced", "heavy_shield", "summoner_cult"], enchant_pool: ["frenzy", "ironhide", "fortress"], elite_base_rate: 0.42 }, // 30: 最终 Boss 30: { base_count: 12, squad_pool: ["assassin_squad", "heavy_shield", "summoner_cult", "mixed_balanced"], enchant_pool: ["frenzy", "ironhide", "swift", "nightmare", "fortress"], boss_wave: true, elite_base_rate: 0.45 }, }; ``` ### 5.5 全局难度缩放 ```typescript export function getGlobalScale(wave: number): { hp_mul: number; ap_mul: number } { const step = Math.floor((wave - 1) / 5); // 每 5 波一档 return { hp_mul: 1 + 0.15 * step, // wave 1=1.0, 6=1.15, 11=1.30, 16=1.45, 21=1.60, 26=1.75 ap_mul: 1 + 0.08 * step, // wave 1=1.0, 6=1.08, 11=1.16, 16=1.24, 21=1.32, 26=1.40 }; } ``` **最终 HP 公式**:`HeroInfo[uuid].hp × 全局乘子 × 波次Enchant乘子 × 个体Elite乘子` ## 6. 生成引擎算法 ### 6.1 主流程伪代码 ```typescript generateWave(waveNumber: number): GeneratedMonster[] { // 1. 取硬编码 WaveConfig (wave > 30 时使用第 30 波配置) const cfg = WaveConfigs[Math.min(waveNumber, 30)]; // 2. 抽取波次级强化(按权重从 enchant_pool 抽 0~2 个) const enchants = pickEnchants(cfg.enchant_pool); // 3. 计算波次最终属性乘子 const globalScale = getGlobalScale(waveNumber); const enchantHpMul = product(enchants.map(e => e.hp_mul ?? 1)); const enchantApMul = product(enchants.map(e => e.ap_mul ?? 1)); const eliteRateMul = product(enchants.map(e => e.elite_rate_mul ?? 1)); // 4. Boss 位(Boss 波首位占 1 个) let monsters: GeneratedMonster[] = []; let remaining = cfg.base_count; if (cfg.boss_wave) { monsters.push(makeBoss(waveNumber)); remaining -= 1; } // 5. 小队拼装:按权重从 squad_pool 抽小队,累计到 remaining monsters.push(...assembleSquads(cfg.squad_pool, remaining, waveNumber)); // 6. 应用全局 & 波次 Enchant 乘子 for (const m of monsters) { m.hp = Math.round(m.hp * globalScale.hp_mul * enchantHpMul); m.ap = Math.round(m.ap * globalScale.ap_mul * enchantApMul); m.wave_enchants = enchants.map(e => e.id); } // 7. 应用个体 Elite(非 Boss 怪) const eliteBaseRate = cfg.elite_base_rate ?? Math.min(0.05 + waveNumber * 0.01, 0.30); for (const m of monsters) { if (!m.isBoss && Math.random() < eliteBaseRate * eliteRateMul) { const elite = pickElite(m); applyElite(m, elite); m.elite = elite; } } return monsters.slice(0, 12); // 硬上限保护 } ``` ### 6.2 关键子算法 #### assembleSquads(小队拼装) ```typescript function assembleSquads(pool: string[], target: number, wave: number): GeneratedMonster[] { const result: GeneratedMonster[] = []; let filled = 0; let safety = 20; // 防止死循环 while (filled < target && safety-- > 0) { const squad = pickWeighted(pool.map(id => SquadLibrary[id])); const remaining = target - filled; for (const slot of squad.slots) { for (let i = 0; i < slot.count && filled < remaining; i++) { result.push(makeMonster(slot.type, wave)); filled++; } } } return result; } ``` - 小队塞不下时按剩余位截断 - `safety` 防御性循环上限 - 同一波可抽到重复小队(多样性来自权重 + 多次循环) #### makeBoss(Boss 生成) ```typescript function makeBoss(wave: number): GeneratedMonster { const isMeleeBoss = Math.random() < 0.5; const type = isMeleeBoss ? MonType.MeleeBoss : MonType.LongBoss; const uuid = pickUuidFromMonList(type); // Boss 自带额外血量(基于波次的固定加成) const baseHp = HeroInfo[uuid].hp; const bossBonusHpMul = 1 + 0.20 * Math.floor((wave - 5) / 5); // wave 5=1.0, 10=1.2, 15=1.4... return { uuid, type, hp: baseHp * bossBonusHpMul, ap: HeroInfo[uuid].ap, isBoss: true, elite: undefined, // Boss 不带个体强化 affixes: [], wave_enchants: [], spawnIndex: 0, }; } ``` #### pickEnchants(波次强化抽取) ```typescript function pickEnchants(pool?: string[]): WaveEnchant[] { if (!pool || pool.length === 0) return []; const enchants: WaveEnchant[] = []; // 第一个 Enchant: 70% 概率抽 1 个 if (Math.random() < 0.7) { enchants.push(pickWeighted(pool.map(id => WaveEnchantLibrary[id]))); } // 第二个 Enchant: 30% 概率再抽 1 个(不重复) if (Math.random() < 0.3 && pool.length > 1) { const second = pickWeighted( pool.filter(id => !enchants.some(e => e.id === id)) .map(id => WaveEnchantLibrary[id]) ); if (second) enchants.push(second); } return enchants; } ``` ### 6.3 GeneratedMonster 扩展接口 ```typescript export interface GeneratedMonster { uuid: number; type: MonType; hp: number; ap: number; isBoss: boolean; spawnIndex: number; /** @deprecated 已被 elite 取代,始终返回 [],仅向后兼容 */ affixes: AffixType[]; // 新增字段 elite?: MonsterElite; // 个体强化(无则 undefined) wave_enchants: string[]; // 该波触发的波次强化 id 列表 // 测试模式专用(保留原逻辑) testSkills?: { ... }; } ``` ### 6.4 关键设计权衡 | 决策 | 选择 | 理由 | |---|---|---| | Boss 是否可带个体强化 | 否 | Boss 已经有额外血量+波次加成 | | Enchant 是否会叠加同种 | 否 | 第二个 Enchant 过滤已抽过的 id,避免数值爆炸 | | 小队截断 | 按剩余位截断 | 保证总怪数 = `base_count` | | 个体 Elite 是否可叠加 | 否(每只最多 1 个) | 简化逻辑、避免叠加 bug | | wave > 30 行为 | 复用 wave 30 配置 + 全局乘子继续增长 | 满足"长线"需求 | ## 7. 错误处理与配置校验 ### 7.1 启动校验 ```typescript export function validateRogueConfig(): string[] { const errors: string[] = []; // 1. 校验 WaveConfigs 中所有 squad_pool / enchant_pool 引用存在 for (const wave in WaveConfigs) { const cfg = WaveConfigs[wave]; for (const squadId of cfg.squad_pool) { if (!SquadLibrary[squadId]) { errors.push(`Wave ${wave} 引用了不存在的小队: ${squadId}`); } } if (cfg.enchant_pool) { for (const enchId of cfg.enchant_pool) { if (!WaveEnchantLibrary[enchId]) { errors.push(`Wave ${wave} 引用了不存在的强化: ${enchId}`); } } } if (cfg.base_count < 1 || cfg.base_count > 12) { errors.push(`Wave ${wave} base_count=${cfg.base_count} 越界 (1~12)`); } } // 2. 校验 SquadLibrary 中所有 type 在 MonList 中有怪 for (const id in SquadLibrary) { const squad = SquadLibrary[id]; for (const slot of squad.slots) { if (!MonList[slot.type] || MonList[slot.type].length === 0) { errors.push(`小队 ${id} 引用的怪物类型 ${slot.type} 在 MonList 中无可用 uuid`); } } } return errors; } ``` ### 7.2 防御性兜底 | 异常情况 | 兜底行为 | |---|---| | `WaveConfigs[wave]` 不存在 | 使用 `WaveConfigs[30]` | | `SquadLibrary[id]` 不存在 | 跳过该小队,记录 warning | | `MonList[type]` 为空 | 使用 `MonList[MonType.Melee]` 兜底 | | 单次 uuid 抽取失败 | 使用 `uuids[0]` 兜底 | | 小队拼装超过 12 只 | 在主流程末尾统一 `slice(0, 12)` | ## 8. 向后兼容性 ### 8.1 AffixType 枚举处理 ```typescript /** @deprecated 已被 MonsterElite 取代,仅保留导出避免破坏引用 */ export enum AffixType { ... } ``` 经 Grep 确认 `AffixType` 仅在 RogueConfig.ts 内部使用。 ### 8.2 接口保留 以下导出保留,调用方不受影响: - `spawningEngine` 单例 - `getWaveMonsterCount(wave)` / `getWaveSlotConfig(wave)` - `DefaultWaveSlot` / `WaveSlotConfig` (Proxy) - `IWaveSlot` 接口 ### 8.3 调用方影响评估 | 文件 | 引用情况 | 改造点 | |---|---|---| | [MissionMonComp.ts](../../../assets/script/game/map/MissionMonComp.ts) | `spawningEngine.generateWave()` / `GeneratedMonster` | 仅读 hp/ap/isBoss,无字段依赖变更,**零改动** | | [MissionComp.ts](../../../assets/script/game/map/MissionComp.ts) | `spawningEngine.reset()` | **零改动** | ## 9. 测试模式适配 `TestModeConfig` 保留原拦截逻辑,仅扩展 2 个可选字段: ```typescript export const TestModeConfig = { enable: false, // ... 原字段保留 affixes: [] as AffixType[], // 新增:测试个体/波次强化的快速开关 testElite?: MonsterElite; testWaveEnchant?: string; }; ``` 测试模式生成的怪物补充新字段(`elite` / `wave_enchants`),保持接口一致性。 ## 10. 文件结构(重构后骨架) ``` RogueConfig.ts ├─ 1. 文件头注释 ├─ 2. 枚举与基础类型 │ ├─ MonsterElite + MonsterEliteSet │ ├─ AffixType(废弃保留) │ ├─ GeneratedMonster(扩展) │ └─ IWaveSlot(兼容保留) ├─ 3. 怪物池(保留原逻辑) │ └─ MonList(从 HeroInfo 动态提取) ├─ 4. 波次强化库 │ ├─ WaveEnchant 接口 │ └─ WaveEnchantLibrary(硬编码 5 条) ├─ 5. 小队模板库 │ ├─ SquadSlot / SquadConfig 接口 │ └─ SquadLibrary(硬编码 6 条) ├─ 6. 波次配置表 │ ├─ WaveConfig 接口 │ └─ WaveConfigs(硬编码 wave 1~30) ├─ 7. 全局难度缩放 │ └─ getGlobalScale(wave) ├─ 8. 配置校验 │ └─ validateRogueConfig() ├─ 9. 生成引擎 │ ├─ RogueSpawningEngine(重写) │ │ ├─ generateWave(wave) │ │ ├─ assembleSquads() │ │ ├─ makeBoss() │ │ ├─ makeMonster() │ │ ├─ applyElite() │ │ ├─ pickElite() │ │ ├─ pickEnchants() │ │ └─ pickWeighted() │ ├─ getWaveMonsterCount()(重写,返回 base_count) │ └─ getWaveSlotConfig()(保留,从生成结果反推) ├─ 10. 测试模式(保留) │ └─ TestModeConfig(扩展 testElite / testWaveEnchant) └─ 11. 全局单例 & 向后兼容导出 ├─ spawningEngine ├─ DefaultWaveSlot └─ WaveSlotConfig (Proxy) ``` ## 11. 验证方式 1. **静态校验**:游戏启动时调用 `validateRogueConfig()`,控制台无 error。 2. **运行时验证**: - 测试模式分别设置 `testElite` / `testWaveEnchant`,验证 hp/ap 计算正确。 - 正常模式连续生成 wave 1~30,检查每波怪物数、Boss 出现、Enchant 命中。 - wave 35 验证全局乘子继续递增。 3. **回归验证**:现有战斗流程不中断,[MissionMonComp](../../../assets/script/game/map/MissionMonComp.ts) 无报错。 ## 12. 风险与缓解 | 风险 | 概率 | 缓解措施 | |---|---|---| | SquadLibrary 中 type 在 MonList 中无怪 | 中 | `validateRogueConfig()` 启动校验 + 兜底 Melee | | 30 波数据量大,手写易出错 | 高 | 设计文档已给出完整 30 波数据,避免实施时现想 | | MissionMonComp 隐式依赖 affixes | 低 | 已检查仅读 hp/ap/isBoss,安全 | | AffixType 在其他文件被引用 | 低 | Grep 已确认仅 RogueConfig.ts 内部使用 | | 配置错误导致死循环 | 低 | assembleSquads 有 safety 计数器 | ## 13. 后续扩展点(本次不实施) - **HeroAttrsComp 升级**:新增 `regen_per_sec` 字段后,启用 `MonsterElite.Regen` 的回血效果。 - **攻速字段统一**:把怪物 CD 也纳入 `getEffectiveSkillCd` 后,启用 `cd_mul`。 - **数据驱动化**:把 WaveConfigs / SquadLibrary / WaveEnchantLibrary 抽到 JSON 配置文件,进一步解耦。 - **强化组合扩充**:WaveEnchantLibrary 可继续扩充(如「元素浪潮」「血月」等主题强化)。