3.8 KiB
3.8 KiB
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.15s–0.35s