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

3.8 KiB
Raw Permalink Blame History

ADR-004: Form Switch State Machine Design

Status: Accepted Date: 2026-04-27 Engine: Unity 2022.3.62f3c1 GDD Requirements Addressed: Form Switch State Machine (systems-index.md #3)


Context

Form switching is Nightborn's core operation — every switch is a risky commitment action. The state machine must balance "responsive feel" against "decisions have weight." It must also support future switch-speed upgrades from the Skill Tree.

Decision

Four-Phase Linear State Machine

                  RequestSwitch(Wolf)
    Idle ─────────────────────────────→ Windup
     ↑                                    │
     │  timer >= recovery                 │  timer >= windup
     │                                    │
     │  ┌─────────────────────────────┐   │
     │  │ Hit during Windup →         │←──│
     │  │ OnSwitchInterrupted         │   │
     │  └─────────────────────────────┘   ↓
     │                               Switching (i-frames)
    Recovery ←───────────────────────────┘
                  timer >= switch

Phase Parameters (baseline, modifiable by Skill Tree)

Phase Duration Can Act Interruptible Can Re-Switch
Idle Full
Windup 0.25s Cannot attack Yes (hit → lose Blood Energy) No
Switching 0.10s Cannot act No (i-frames) No
Recovery 0.15s Can attack/move No No

Total switch time: 0.50s. Danger window: 0.25s (Windup).

Interrupt Rules

  • Hit during Windup → OnSwitchInterrupted → Blood Energy already spent, switch fails → return to Idle
  • This IS the "switched at the wrong time" punishment — core to the Switch is Commitment pillar

Post-Switch Cooldown

After Recovery ends, an additional 0.3s cooldown before another switch is allowed (minimum 0.8s between switches) — prevents switch spam and preserves rhythmic feel.

Implementation

// L0 — Pure C#, zero Unity
public class FormSwitchStateMachine
{
    public FormType CurrentForm { get; private set; } = FormType.Human;
    public SwitchPhase Phase { get; private set; } = SwitchPhase.Idle;
    public float PhaseTimer { get; private set; }

    // Tunable (Skill Tree can modify)
    public float WindupDuration = 0.25f;
    public float SwitchDuration = 0.10f;
    public float RecoveryDuration = 0.15f;
    public float CooldownDuration = 0.3f;

    public SwitchResult RequestSwitch(FormType target, BloodEnergyEconomy energy);
    public void Update(float deltaTime);
    public void Interrupt();

    public event Action<FormType, FormType> OnFormChanged;
    public event Action<SwitchPhase> OnPhaseChanged;
    public event Action<FormType> OnSwitchInterrupted;
}

Why a Simple Linear FSM

  • 4 phases progress sequentially — no Hierarchical FSM or Behavior Tree needed
  • Only Idle accepts input — logic is straightforward for external developers
  • If a future form needs a different flow, it can subclass with its own state machine

Consequences

  • Switch feel is controlled by exactly 4 parameters (WindupDuration, SwitchDuration, RecoveryDuration, CooldownDuration) — tuning is centralized
  • Interrupt during Windup creates clear "wrong time to switch" punishment, reinforcing Switch is Commitment
  • Skill Tree can directly modify these 4 parameters for switch-speed upgrades
  • Windup duration (0.25s) is the critical feel parameter — MUST be validated through prototype, likely iterating between 0.15s0.35s