Vampire-Act-Mono/docs/architecture/adr-005.md

3.4 KiB

ADR-005: Enemy AI Decision Model

Status: Accepted Date: 2026-04-27 Engine: Unity 2022.3.62f3c1 GDD Requirements Addressed: Enemy AI Logic, Boss AI Logic (systems-index.md #4, #5)


Context

Nightborn's enemy AI doesn't need complex tactical reasoning, but has a special requirement: each enemy type must strongly favor a specific form, creating "I need to switch" pressure. MVP scope is 3-5 enemy types. The AI model must be simple to implement, produce predictable behavior, and be easily understood and tested by external developers.

Decision

Use lightweight per-type Finite State Machines. Reject Behavior Trees and Utility AI.

Base FSM Structure (shared by all enemy types)

    Idle ──→ Chase ──→ Attack ──→ Cooldown
      ↑                    │          │
      └────────────────────┴──────────┘
      If too far, return to Chase

Per-Type Differentiation in the Attack State

Enemy Type Attack Behavior Encouraged Form Why
Swarm Many simultaneous small-area hits Mist Mist's circular AOE clears groups at once
Brute Slow, high-damage single strike Human Human's parry window is clear and precise against it
Stalker Fast rush + point-blank flurry Wolf Wolf's rectangular dash collides for a stagger counter
Shooter Ranged projectile barrage from periphery Mist/Wolf Mist phases through to close, or Wolf bursts to eliminate
Boss Multi-phase mixed patterns All forms Phase transitions require form adaptation

Why Not Behavior Trees

BTs excel at complex decision chains (e.g., FPS cover-shooter AI). Nightborn enemies need only 4 states. The tree's maintenance cost exceeds its value.

Why Not Utility AI

Scoring systems produce "optimal" behavior but aren't predictable enough — players can't learn enemy patterns, violating the "Battlefield is Information" pillar.

Implementation

// L0 — Pure C#, returns one AICommand per frame
public enum EnemyStateType { Idle, Chase, Attack, Cooldown }

public class EnemyAILogic
{
    public EnemyTypeConfig Config;

    public AICommand ComputeAction(EnemyState self, PlayerState player)
    {
        TransitionState(self, player);
        return CurrentState switch
        {
            Idle     => ScanForPlayer(player),
            Chase    => MoveToward(player),
            Attack   => ExecuteAttack(self.AttackPattern, player),
            Cooldown => WaitAndReset(self.CooldownTimer),
        };
    }

    // Per-type override for unique transition logic
    protected virtual void TransitionState(EnemyState self, PlayerState player) { }
}

Boss Extension

Boss AI extends the base FSM with:

  • BossPhase enum (additional state dimension)
  • Phase transition conditions (health thresholds, timer triggers)
  • OnPhaseChanged event for L1 VFX/Audio synchronization

Consequences

  • External developers can understand an enemy's behavior from EnemyTypeConfig alone — no need to parse behavior trees
  • Enemy behavior is predictable and learnable — satisfies "Battlefield is Information" pillar
  • Each enemy type's Attack pattern naturally points to one form counter — reinforces "Form is Tactics"
  • If future enemies need more intelligence (cover, coordination), the FSM base can be extended without replacement