Vampire-Act-Mono/design/gdd/combat-logic.md

213 lines
11 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Combat Logic
> **Status**: In Design
> **Author**: SepComet + Claude
> **Last Updated**: 2026-04-27
> **Implements Pillar**: 操作优于数值 (Skill Over Stats), 战场即信息 (Battlefield is Information)
## Overview
Combat Logic 是夜裔的即时战斗判定核心——负责"打中了没有""伤害是多少""是否暴击"。它接收攻击数据(形状、伤害值、来源形态),对目标进行几何相交检测,产出 DamageResult。系统完全纯C#,使用 ADR-003 定义的 Circle/Rect/Sector 进行形状判定。它不产生任何视觉或音频效果——而是通过 C# event 将结果传递给 L1/L2。设计原则**判定必须确定、反馈必须清晰、计算必须快速**(每帧数百次判定在 1ms 内完成)。
## Player Fantasy
夜裔的战斗幻想不是"数字在涨",而是**每一击都有形状、有重量、有来由**。当你用人形精准弹反——扇形判定区恰好覆盖精英的攻击前摇——一个金色暴击数字弹出,你感觉自己不是"按了攻击键",而是用几何体"接住了"对方的攻击。当你切到狼躯冲刺——矩形判定贯穿一排敌人——敌人被击退的轨迹本身就是你所画的一条线。当雾形圆形AOE笼罩整个屏幕的杂兵那种"我画了一个圈,圈里全死"的掌控感,就是 Combat Logic 要传达的幻想。参考 RE4 弹反的"接住"反馈 + DMC 的判定可视化 + 割草的密度感。
## Detailed Design
### Attack Shape Definitions
Each form maps to one attack geometry type (via ADR-003):
| Form | Shape | Parameters | Visual Read |
|------|-------|------------|-------------|
| **Human** | `Sector` | Angle=90°, Radius=3.0 | Forward fan — parry judgment zone |
| **Wolf** | `Rect` | Width=1.5, Length=5.0 | Forward rectangle — dash trajectory |
| **Mist** | `Circle` | Radius=5.0 | Self-centered circle — full AOE |
### Data Structures
```
AttackData {
Vector3 Origin // Attack origin (player position)
float Direction // Facing angle
Shape HitShape // Circle / Rect / Sector
FormType SourceForm // Form that launched the attack
float BaseDamage // From Form Switch SM's AttackStyle
float CritChance // Base 0.05, upgradable
float CritMultiplier // Default 1.5x
float KnockbackForce
}
DamageResult {
int TargetId
float FinalDamage
bool IsCritical
Vector3 HitPoint // For VFX positioning
bool IsKillingBlow
FormType SourceForm
}
```
### Hit Resolution Pipeline (called once per frame)
```
CombatLogic.ResolveAttacks(attacks, enemies, deltaTime)
1. COLLECT: Receive List<AttackData> + List<EnemyState>
2. CULL: Remove dead enemies, skip enemies outside attack range (coarse filter)
3. INTERSECT: For each attack × potential target, call ADR-003 Shape.Intersects()
4. DAMAGE: For each hit pair, execute damage formula (Section D)
5. SORT: Descending by damage — high damage resolves first
6. DEDUPLICATE: Same attack can hit multiple enemies; same enemy can be hit by multiple attacks — but each attack hits each enemy at most once per frame
7. EMIT: OnHit(DamageResult), OnKill(DamageResult), OnCrit(DamageResult)
8. RETURN: List<DamageResult> grouped by TargetId
```
### Target Filtering Rules
- Enemy state = Dead → skip
- Attack shape has no intersection with enemy hit shape → skip
- Same attack already hit this enemy this frame → skip (dedup)
### Events
| Event | Trigger | Payload |
|-------|---------|---------|
| `OnHit` | Every successful hit | `DamageResult` |
| `OnKill` | Hit reduces enemy health to 0 | `DamageResult` |
| `OnCrit` | Hit triggers critical | `DamageResult` |
| `OnAttackResolved` | Attack resolution complete (fires even if 0 hits) | `AttackData, int hitCount` |
### Interactions with Other Systems
| System | Direction | Interface |
|--------|-----------|-----------|
| Form Switch SM | Upstream | Reads `AttackStyle` for shape + base damage per form |
| Enemy AI Logic | Downstream | Enemy receives damage, updates health, may trigger behavior change |
| VFX Spawner (L1) | Downstream | Subscribes to `OnHit`/`OnKill`/`OnCrit` for visual feedback |
| Death/Respawn | Downstream | `OnKill` with IsKillingBlow triggers death check |
## Formulas
### Damage Formula
```
finalDamage = baseDamage × critFactor
where critFactor = critMultiplier if random(0,1) < critChance, else 1.0
```
| Variable | Symbol | Type | Range | Description |
|----------|--------|------|-------|-------------|
| Base damage | `baseDamage` | float | 550 | From AttackStyle, determined by form and upgrades |
| Crit chance | `critChance` | float | 0.00.30 | Baseline 0.05, grows via Skill Tree |
| Crit multiplier | `critMultiplier` | float | 1.23.0 | Baseline 1.5 |
| Crit factor | `critFactor` | float | 1.0 or critMultiplier | Dice roll per hit |
**Output Range**: 5150 (extreme: baseDamage=50 × critMultiplier=3.0 = 150)
**Design Intent**: Combat Logic applies only base damage. Enemy resistance modifiers and form affinity bonuses are defined in Enemy AI GDD. Skill damage bonuses are defined in Skill Tree GDD. This keeps Combat Logic single-responsibility.
### Knockback Formula
```
knockbackDistance = knockbackForce / enemyWeight × (IsCritical ? 1.5 : 1.0)
```
| Variable | Symbol | Type | Range | Description |
|----------|--------|------|-------|-------------|
| Knockback force | `knockbackForce` | float | 010 | From AttackStyle |
| Enemy weight | `enemyWeight` | float | 110 | Heavier enemies resist knockback |
**Output**: 015 units | Critical hits add 1.5× knockback
## Edge Cases
| # | Condition | Resolution | Rationale |
|---|-----------|------------|-----------|
| 1 | Attack shape has zero area (radius=0, width=0) | Skip, return empty list | Degenerate shape — no valid hit possible |
| 2 | Enemy has no hit shape defined | Skip that enemy | Incomplete data — should not happen at runtime |
| 3 | Same attack hits same enemy twice (edge overlap) | Dedup: count only first hit | One attack = one hit per target per frame |
| 4 | `critChance` exceeds 1.0 via buffs | Clamp to 1.0 | Guaranteed crit is the ceiling |
| 5 | `baseDamage` = 0 (status effect attack) | Still perform hit test, emit OnHit with 0 damage | Knockback may still apply |
| 6 | Multiple attacks in same frame against same enemy | Resolve all, sort by damage desc | Independent attacks stack |
| 7 | Enemy killed mid-frame by attack #1 | Attacks #2-N skip this enemy (dead filter) | Dead enemies don't accumulate damage |
| 8 | `knockbackForce` = 0 | Return 0, skip calculation | No force applied |
| 9 | deltaTime = 0 (pause) | Return empty list, no events | Paused game = no combat resolution |
| 10 | Empty enemies list | Return empty list, no events | No targets to hit |
| 11 | Shape intersection at exact tangent point | Count as miss (requires overlap > epsilon) | Prevents degenerate edge-touch = hit |
## Dependencies
| System | Relationship | Interface |
|--------|-------------|-----------|
| Form Switch SM | Upstream | Reads `AttackStyle.BaseDamage`, `AttackStyle.AttackShape` per form |
| Enemy AI Logic | Downstream | Enemy receives `DamageResult` for health subtraction and behavior response |
| VFX Spawner (L1) | Downstream | Subscribes to `OnHit`, `OnKill`, `OnCrit` for visual feedback |
| Death/Respawn | Downstream | `OnKill(IsKillingBlow=true)` triggers death check |
| ADR-003 Geometry | Foundation | Uses `Circle`, `Rect`, `Sector` for all intersection tests |
No upstream GDD dependencies — this is a Foundation layer system. Form Switch SM GDD is undesigned; the expected `AttackStyle` contract is defined here as provisional until that GDD is written.
## Tuning Knobs
| Parameter | Default | Safe Range | Too Low | Too High |
|-----------|---------|------------|---------|----------|
| `baseDamage` (Human) | 15 | 825 | Parry feels unrewarding | Parry out-damages other forms |
| `baseDamage` (Wolf) | 25 | 1540 | Burst doesn't feel bursty | Wolf becomes the only viable form |
| `baseDamage` (Mist) | 8 | 415 | AOE feels useless | Mist clears everything, no switching needed |
| `critChance` | 0.05 | 0.020.10 | Crits never seen | Crits too frequent, lose impact |
| `critMultiplier` | 1.5 | 1.22.5 | Crits not noticeable | Damage spikes too wild |
| Human Sector Angle | 90° | 60120° | Too narrow, parry frustrating | Too wide, parry trivial |
| Wolf Rect Length | 5.0 | 3.08.0 | Dash feels short | Range competes with Mist |
| Mist Circle Radius | 5.0 | 3.08.0 | AOE feels cramped | Covers whole screen, no positioning |
## Visual/Audio Requirements
Combat is a visual system — Visual/Audio is REQUIRED.
**VFX (via L1 VFX Spawner):**
- `OnHit`: Geometric particle burst at `DamageResult.HitPoint` — color matches `SourceForm`
- `OnCrit`: Larger burst + screen shake micro — gold/amber tint
- `OnKill`: Enemy shatter into geometric fragments — fragment color = enemy type
- Debug overlay: Semi-transparent fill of Sector/Rect/Circle during attack frames
**Audio (via L1 Audio Player):**
- `OnHit`: Impact SFX — varies by form (Human=sharp slash, Wolf=heavy thud, Mist=ethereal whoosh)
- `OnCrit`: Distinct "clink" + bass emphasis
- `OnKill`: Shatter sound — pitch varies by enemy size
**Art Bible Alignment**: Principle 1 (Color is Identity — hit VFX color = form color), Principle 3 (Particles are Feedback — geometric fragments = information).
## UI Requirements
| Element | Position | Content |
|---------|----------|---------|
| Damage Numbers | Floating at hit point | Numeric value, color = form color, crit = larger + gold |
| Hit Indicator | Screen edge | Directional flash when player takes damage |
| Kill Counter | HUD | Combo/kill streak (fed by `OnKill` event) |
## Acceptance Criteria
| # | GIVEN | WHEN | THEN |
|---|-------|------|------|
| 1 | Player in Human form, enemy in sector zone | `ResolveAttacks` called | Hit detected, `OnHit` fires with SourceForm=Human |
| 2 | Player in Wolf form, enemy 6 units away | `ResolveAttacks` (Rect length=5) | No hit (beyond range), 0 results |
| 3 | Player in Mist form, 5 enemies in 4-unit radius | `ResolveAttacks` (Circle radius=5) | All 5 enemies hit, 5 results returned |
| 4 | Enemy at exact edge of shape boundary | Intersection test | Miss (epsilon threshold > tangent) |
| 5 | critChance=0.3, 100 attacks | `ResolveAttacks` × 100 | ~30 hits critical (±5 tolerance) |
| 6 | Enemy health=10, baseDamage=15 | Hit resolves | `OnKill` fires, IsKillingBlow=true |
| 7 | Enemy health=10, baseDamage=5 | Hit resolves | Health → 5, `OnHit` fires, no `OnKill` |
| 8 | knockbackForce=10, enemyWeight=2 | Hit resolves | knockbackDistance = 5.0 |
| 9 | Dead enemy in list | `ResolveAttacks` | Dead enemy skipped |
| 10 | critChance buffed to 1.2 | Crit roll | Clamped 1.0, all hits crit |
| 11 | 3 attacks vs same enemy in one frame | `ResolveAttacks` | 3 independent hits, damage summed |
| 12 | Attack misses everything | `ResolveAttacks` | `OnAttackResolved` fires with hitCount=0 |
| 13 | deltaTime=0 (pause) | `ResolveAttacks` | Returns empty, no events |
## Open Questions
- Should enemies have different hit shape sizes (e.g., Brute larger than Swarm) or uniform? — Defer to Enemy AI GDD
- Should there be a "perfect timing" bonus for Human parry (hitting within narrow enemy attack window)? — Candidate for Skill Tree / Form Switch SM GDD
- Attack shape size scaling: should Skill Tree upgrades increase shape dimensions, or only damage? — Defer to Skill Tree GDD