# 深度战斗机制扩展设计文档 (Advanced Combat Mechanics Design)

> **⚠️ 实现现状 (2026-07-20 审计)**：本文档的多个进阶维度**未实现**：① §2 连锁/弹跳（`visited_targets` 排除表、`pow(0.9, bounce)` 衰减）——`bounce_remaining` 字段从不被消费；② §3.0 StatusType 现从 `data/status_effects.json`（JSON）加载而非 `res://resources/status_types/*.tres`，且 `can_catalyze`/`immune_faction_tags` 不解析；③ §3.3 催化/覆盖/免疫状态交互未实现；④ §4 伤害公式的 `(1-Res)` 抗性项虽存在于 `calc_damage`，但实战路径恒传 `resistance=0`（死维度）。已实现：DoT/伤害基础管线、DamageContext（含 `mult`，伤害计算在 EnemyManager/SRP）。详见 [审计报告](../../docs_dev/doc_code_audit_2026-07-20.md)。

为了支持 Roguelike 游戏中涌现式的玩法组合（如：闪电链、叠毒流、触发流），现有的简单"伤害+阵营"模型需要升级为多维度的**上下文感知 (Context-Aware)** 系统。

本文档详细定义了扩展战斗深度所需的关键维度和判断逻辑。

## 1. 投射物身份与溯源 (Projectile Identity & Sourcing)

在复杂的战斗中（例如：玩家发射的子弹击中怪物A，触发爆炸，爆炸产生碎片击中怪物B），系统必须能追踪伤害的根本来源。

### 1.1 来源层级 (Source Hierarchy)
我们需要在子弹数据中维护一个"血缘关系"，通常通过 ID 或 引用 实现：
*   **Root Source (根源)**: 也就是 `Owner`。通常是 PlayerEntity 或 EnemyEntity。用于判定击杀归属（触发"击杀回血"天赋）、伤害统计。
*   **Immediate Source (直接来源)**: 发射该子弹的实体。可能是玩家手中的法杖，也可能是另一个"母子弹"（在分裂/触发机制中）。
    *   *应用场景*: 伤害衰减计算。如果是"母子弹"分裂出的"子弹"，继承伤害时可能需要乘系数 0.5。

### 1.2 施法类型标记 (Cast Type Tags)
除了 `Faction` (阵营)，还需要 `Tags` (位掩码) 来区分伤害性质，以便应用特定的加成：
*   **Tag: Primary**: 玩家直接射击（享受"普攻伤害加成"）。
*   **Tag: Triggered**: 由触发器产生的（享受"技能伤害加成"，但可能不触发某些普攻特效）。
*   **Tag: Summon**: 召唤物造成的伤害。
*   **Tag: Reflection**: 反弹/弹反造成的伤害。

## 2. 连锁与弹跳逻辑 (Chain & Bounce Dimensions)

"闪电链"不仅仅是次数的递减，它需要一个完整的状态机来处理复杂的传递逻辑。

### 2.1 命中历史 (Hit History / Exclusion List)
*   **问题**: 简单的"最近距离"查找会导致闪电在两个靠近的怪物之间无限来回弹射 (Ping-Pong)。
*   **维度需求**: 每颗子弹需要维护一个已命中目标 ID 集合 `visited_targets: Array[int]`。
*   **逻辑**: `SpatialGrid.find_nearest(pos)` 过滤掉 `visited_targets` 中已包含的 `entity_id`。
*   **性能优化**: 在 ECS 架构中，可以使用 `Dictionary` (key: bullet_id int, value: Array[int]) 的 Sidecar 模式存储历史记录，仅在命中结算（低频逻辑）时查询，不影响移动更新（高频逻辑）。

### 2.2 衰减模型 (Decay Models)
每次传递后的伤害变化不应只有一种模式：
*   **Linear Decay**: `Dmg = Base - (BounceCount * FixedValue)`
*   **Exponential Decay**: `Dmg = Base * (DecayFactor ^ BounceCount)` (例如每次衰减 20%)
*   **Reverse Scaling**: `Dmg = Base * (1 + BounceCount * 0.1)` (越弹伤害越高，奖励多次传递)

### 2.3 传递约束 (Propagation Constraints)
*   **Max Distance**: 弹射的最大搜索半径（不能跨越半个屏幕弹射）。
*   **Line of Sight**: 弹射是否需要视野（能否穿墙）。
*   **Angle Limit**: 只能向前弹射（90度扇区），不能向后弹射。

## 3. 状态效果与持续伤害 (Status Effects & DoT)

毒、火、流血等机制需要独立且统一的 **Status Manager**。

### 3.0 StatusType 数据驱动方案 (E4)

`StatusType` **不使用硬编码枚举**，而是从 `res://resources/status_types/` 目录自动加载 `.tres` Resource，实现完全数据驱动。新增状态类型无需修改引擎代码：

```gdscript
# status_type_def.gd
class_name StatusTypeDef
extends Resource

@export var id: int                    # 全局唯一整数 ID（用于位掩码和 Dictionary key）
@export var name: String               # 显示名称
@export var stack_mode: int            # 0=Duration, 1=Intensity, 2=Independent
@export var max_stacks: int = 99       # 最大叠加层数（0=无限）
@export var tick_interval: float = 1.0 # DoT 跳字间隔（秒）
@export var can_catalyze: Array[int]   # 催化反应：触发时需要的另一状态 ID 列表
@export var immune_faction_tags: int   # 免疫此状态的 faction 位掩码
```

`StatusManager._ready()` 启动时扫描目录注册，逻辑与 SpellRegistry 完全一致。

### 3.1 堆叠维度 (Stacking Dimensions)
*   **Duration Stacking (时间堆叠)**: 再次施加相同效果，刷新或延长持续时间（如：冰冻）。
*   **Intensity Stacking (强度堆叠)**: 再次施加相同效果，增加层数，伤害倍率提升（如：中毒，1层每秒10点，5层每秒50点）。
*   **Independent Instances (独立计算)**: 每次施加都是一个独立的 DoT 计时器，互不干扰（如：流血，身上可以挂10个流血Debuff，每个单独跳字）。

### 3.2 结算归属 (Tick Attribution)
DoT 造成的每次伤害（Tick）都需要携带 `Root Source` 信息。
*   *场景*: 玩家给 Boss 挂了毒，然后跑开了。Boss 被毒死时，系统必须知道是"玩家"杀死的，从而触发"通关结算"或"击杀回血"。

### 3.2.5 StatusManager Tick 机制（P-N4 补充，P5-N1 修正）

`StatusManager` 绑定 `_physics_process(delta)`（60 Hz），采用**批处理 Tick**策略：

- **平铺数组存储（P5-N1）**：`_active_statuses: Array[StatusInstance]` 为**一维平铺数组**，每个 `StatusInstance` 携带 `entity_id` 字段。相较于原 `Dictionary{entity_id → Array}` 方案，平铺顺序遍历的缓存命中率显著更高，规避了 GDScript 哈希表迭代的无序开销。按实体查询（`get_instances_for(entity_id)`）仅在 apply/remove 等低频操作时调用，不出现在 `_physics_process` 热路径中。
- **每帧遍历**：逆序遍历 `_active_statuses`，按各实例的 `remaining_duration` 递减 `delta`，duration ≤ 0 时原地 swap-with-last 移除（O(1)），避免遍历中途修改数组。
- **按 tick_interval 跳字**：DoT 不在每帧触发伤害，而是维护 `tick_accumulator: float`；
  每帧 `tick_accumulator += delta`，当 `tick_accumulator >= tick_interval` 时
  触发一次 `APPLY_DAMAGE` 并扣减 `tick_interval`（允许 delta 跨多个 tick_interval 时补算多次）。
- **分帧批处理**：若同帧活跃状态实例超过 `STATUS_BATCH_LIMIT = 500` 条，
  按 `inst.entity_id % 2` 奇偶帧分批处理，牺牲 1 帧的 tick 精度换取帧时间平摊。

```gdscript
# _active_statuses: Array[StatusInstance]（平铺数组，非 Dictionary）
# StatusInstance 字段: entity_id, type_def, intensity, remaining_duration, tick_accumulator, root_owner_id
func _physics_process(delta: float) -> void:
    var i := _active_statuses.size() - 1
    while i >= 0:
        var inst: StatusInstance = _active_statuses[i]
        inst.remaining_duration -= delta
        inst.tick_accumulator  += delta
        while inst.tick_accumulator >= inst.type_def.tick_interval:
            inst.tick_accumulator -= inst.type_def.tick_interval
            _apply_dot_tick(inst.entity_id, inst)
        if inst.remaining_duration <= 0.0:
            _active_statuses[i] = _active_statuses.back()
            _active_statuses.pop_back()
        i -= 1
```

### 3.3 状态交互矩阵 (Interaction Matrix)
状态之间应有预定义的交互规则：
*   **Overwrite**: 强状态覆盖弱状态（大冰冻覆盖小减速）。
*   **Catalyze (催化)**: 状态 A 遇到 状态 B -> 消除 B 并触发 C（雷 + 湿 = 范围感电）。
*   **Immunity**: 某些怪物（如火元素）对特定状态（燃烧）免疫甚至回血。

## 4. 伤害计算管线 (The Damage Pipeline)

伤害计算遵循 `docs/design/numerical_design.md §3.1` 定义的权威公式：

```
FinalDamage = ((Base + Add) × Mult) × (1 - Res) - Armor
```
其中：`Mult` = `SourceMultipliers × CriticalMultiplier`；`Res` = 敌人元素抗性 (0.0~1.0)；`Armor` = 敌人护甲平铺扣除值。最终伤害最小为 1。

我们需要在伤害事件 `APPLY_DAMAGE` 中传递一个结构体 `DamageContext` 而非简单的数值：

```gdscript
# damage_context.gd - 继承 RefCounted，通过对象池复用
# 权威实现（含池化逻辑）在 DamageContextPool Autoload；本文件描述字段语义。
class_name DamageContext
extends RefCounted

var base_damage: float = 0.0    # 基础数值（含 CastStats.damage_add）
var mult:        float = 1.0    # 伤害乘算系数（暴击 × 元素弱点已合入此值）
                                # 对应公式：Damage = ((Base + Add) × Mult) × (1 - Res) - Armor
                                # EnemyManager.apply_damage() 读取此字段计算最终伤害
var damage_type: int = DamageType.PHYSICAL  # DamageType 枚举
var owner_id: int = -1          # 来源实体 ID（Root Owner）
var is_crit: bool = false       # 是否暴击（影响爆字/特效触发；与 DamageContextPool 字段同步）
var pierce_rate: float = 0.0    # 护甲穿透率（0.0~1.0；与 DamageContextPool 字段同步）
var source_tags: int = 0        # 位掩码: PRIMARY=1, TRIGGERED=2, SUMMON=4, REFLECTED=8

func reset() -> void:
    base_damage = 0.0; mult = 1.0; damage_type = DamageType.PHYSICAL; owner_id = -1
    is_crit = false; pierce_rate = 0.0; source_tags = 0
    # mult 必须重置为 1.0，否则池化复用时上帧的暴击乘算残留到下次计算
```

## 5. 实现建议 (ECS Adaptation)

针对当前的 `BulletManager` (PackedFloat32Array ECS)，如何在不破坏性能的前提下引入这些维度？

**混合架构 (Hybrid ECS Pattern):**

1.  **热数据 (PackedFloat32Array)**: 保持 `x, y, vx, vy, life` 在 `PackedFloat32Array` 中，确保每帧移动批处理极快。
2.  **冷数据 (Dictionary)**: 在 `BulletManager` 中维护 `_bullet_contexts: Dictionary`（key = bullet_id `int`）。
    *   值为 `BulletContext` 对象（继承 `RefCounted`，通过对象池复用），存储 `visited_targets`、`owner_id`、`proc_coefficient`、`on_hit_payload_id` 等复杂逻辑。
    *   仅在 `_on_bullet_hit()`（命中）或 `spawn()`（生成）时访问 Dictionary，这些事件频率远低于每帧移动，不会成为瓶颈。

```gdscript
# bullet_context.gd - 通过 ObjectPool 复用，避免 GC 抖动
class_name BulletContext
extends RefCounted

var owner_id: int = -1               # 根来源实体 ID
var visited_targets: Array[int] = [] # 连锁弹射已命中目标列表
var proc_coefficient: float = 1.0    # 触发系数（加特林子弹通常设为 0.1）
var on_hit_payload_id: int = -1      # 命中时触发的 SubPayload ID（-1=无）
var bounce_count: int = 0            # 已弹射次数
# BulletContext 不持有伤害计算逻辑（SRP）。
# 弹射衰减（pow(0.9, bounce_count)）集中在 EnemyManager.apply_damage() 中实现；BulletContext 仅透传 bounce_count。

func reset() -> void:
    owner_id = -1; visited_targets.clear()
    proc_coefficient = 1.0; on_hit_payload_id = -1; bounce_count = 0
```

通过这种方式，热路径（移动积分）保持纯 Array 操作，冷路径（命中逻辑）通过 Dictionary 访问复杂上下文，兼顾了弹幕游戏的性能与 RPG 的深度。
