Files
pixelheros/docs/superpowers/specs/2026-07-05-rogue-config-refactor-design.md
panFD 251caf0b36 docs(rogue): add refactor design for rule-based spawning
Replace pure-random spawning with hard-coded wave configs, modular squad templates, and dual-layer enchant system (wave + individual).
2026-07-05 22:29:08 +08:00

24 KiB
Raw Blame History

RogueConfig 重构设计:从纯随机到规则化随机组合

  • 创建日期: 2026-07-05
  • 状态: 已批准(待实施)
  • 作用域: 重构 assets/script/game/map/RogueConfig.ts 及配套调用方
  • 目标: 把肉鸽刷怪系统从「每只怪独立随机」改造为「在硬编码规则内的随机组合变动」

1. 背景与问题

当前 RogueConfig.tsRogueSpawningEngine.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 改动最小化。

3. 设计决策

决策点 选择 理由
组合颗粒度 小队模块化3-4 只怪/小队) 每波拼装 2-4 个小队,变化丰富且可控
强化层级 双层混合(波次级 + 个体级) 既保留波次"环境"主题感,又有单体惊喜
波次配置组织 按波次逐条硬编码 每波字段完整独立,调参最直接
硬编码范围 wave 1~30 + 超出复用 wave 30 长线体验,超出后由全局乘子维持难度增长
个体强化种类 精选 5 种Elite/Berserk/Shield/Swift/Regen 数量适中,调参简单
Boss 节奏 每 5 波一个 Boss5/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.tsatk_cd / regen_per_sec 字段。本次先不改造 HeroAttrsCompSwift/Regen 简化为「数值差异化」cd_mul 与 regen 作为后续扩展点留口子。

export enum MonsterElite {
    Elite    = 0, // 精英:全面强化
    Berserk  = 1, // 狂暴:高攻低血
    Shield   = 2, // 护盾:坦克
    Swift    = 4, // 疾风:平衡偏输出
    Regen    = 3, // 再生:高血偏弱攻
}

export const MonsterEliteSet: Record<MonsterElite, {
    name: string;
    hp_mul: number;
    ap_mul: number;
}> = {
    [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

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<string, WaveEnchant> = {
    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: "个体强化率×2HP+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

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<string, SquadConfig> = {
    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

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<number, WaveConfig> = {
    // 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 全局难度缩放

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 主流程伪代码

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小队拼装

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 防御性循环上限
  • 同一波可抽到重复小队(多样性来自权重 + 多次循环)

makeBossBoss 生成)

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波次强化抽取

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 扩展接口

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 启动校验

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 枚举处理

/** @deprecated 已被 MonsterElite 取代,仅保留导出避免破坏引用 */
export enum AffixType { ... }

经 Grep 确认 AffixType 仅在 RogueConfig.ts 内部使用。

8.2 接口保留

以下导出保留,调用方不受影响:

  • spawningEngine 单例
  • getWaveMonsterCount(wave) / getWaveSlotConfig(wave)
  • DefaultWaveSlot / WaveSlotConfig (Proxy)
  • IWaveSlot 接口

8.3 调用方影响评估

文件 引用情况 改造点
MissionMonComp.ts spawningEngine.generateWave() / GeneratedMonster 仅读 hp/ap/isBoss无字段依赖变更零改动
MissionComp.ts spawningEngine.reset() 零改动

9. 测试模式适配

TestModeConfig 保留原拦截逻辑,仅扩展 2 个可选字段:

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 无报错。

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 可继续扩充(如「元素浪潮」「血月」等主题强化)。