# 130 — Central State Authority (`BPC_StateManager`)

> **⚡ C++ Status: Full Implementation** — `Source/PG_Framework/Public/Player/BPC_StateManager.h` provides the complete state authority: `IsActionPermitted()` hot-path query, `RequestStateChange()`, `ForceStateChange()`/`RestorePreviousState()` force stack, heart rate BPM smoothing with tier detection, gating rule evaluation. **Attach directly to player pawn** (component slot 0). Set `GatingTable` → `DA_StateGatingTable` in Details panel. Do NOT create a BP child. See `docs/developer/cpp-integration-guide.md` for usage patterns.
>
> ---

## Purpose
Single source of truth for "what can the player do right now?" Every system queries `IsActionPermitted(Tag)` instead of checking other systems' states directly. Manages exclusive action states, upper-body overlay states, action action gating, vital signs (heart rate), and the force-stack pattern for nested overrides (death, cutscenes, void space). Communicates with GASP exclusively through overlay state variables — never touches motion matching internals.

## Dependencies
- **Requires:** `GI_GameFramework` (04 — game phase changes), `BPC_HealthSystem` (08 — death + injury bindings), `BPC_StressSystem` (10 — stress tier), `BPC_StaminaSystem` (09 — exhaustion), `BPC_MovementStateSystem` (11 — movement mode tracking), `DA_GameTagRegistry` (01 — tag queries), `SS_EnhancedInputManager` (128 — context switching), `ABP_GASP` (external — overlay + intensity variables)
- **Required By:** EVERY gameplay system (central query point for action permission)
- **Engine/Plugin Requirements:** GameplayTags plugin, GASP plugin (read-only)

## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Player/BPC_StateManager.uasset` |
| **Implements Interfaces** | `I_AnimNotifyInterface` (receives GASP notifies) |

---

## 1. Enums

### E_PlayerActionState — Exclusive Action States (45 values)

```text
Enum Name: E_PlayerActionState
(DisplayName = "Player Action State")

Values:
  Idle = 0                    // Standing, not performing any action
  Walking = 1                 // Moving at walk speed
  Jogging = 2                 // Moving at jog speed
  Sprinting = 3               // Moving at sprint speed (stamina draining)
  Crouching = 4               // Crouched posture, possibly moving
  Crawling = 5                // Prone, crawling
  Sitting = 6                 // Sitting on surface — immobile, can look around
  Vaulting = 7                // Transient — vaulting over obstacle
  Climbing = 8                // Ledge or ladder climbing
  Sliding = 9                 // Transient — sliding under or down
  Interacting = 10            // Performing a hold/press interaction
  Inspecting = 11             // Inspecting an item in close-up view
  InInventory = 12            // Inventory menu is open
  InJournal = 13              // Journal/document viewer is open
  InPauseMenu = 14            // Pause menu is open
  InDialogue = 15             // In dialogue with an NPC
  InCutscene = 16             // Pre-authored cutscene playing
  SwingMelee = 17             // Performing a melee attack
  AimingFirearm = 18          // Aiming a firearm
  FiringFirearm = 19          // Firing a firearm (transient)
  Reloading = 20              // Reloading a firearm
  DeployingShield = 21        // Raising a shield
  Hiding = 22                 // Inside a hiding spot (enclosed/behind/under/shadow)
  Peeking = 23                // Peeking from behind cover
  GrabbingObject = 24         // Holding a physics object
  UsingObject = 25            // Using a world object (lever, panel, etc.)
  Dead = 26                   // Dead — death animation playing or death loop active
  InVoidSpace = 27            // In alt death / void space
  InPuzzle = 28               // Using a puzzle device
  InTrialScenario = 29        // In a timed trial scenario
  InMainMenu = 30             // At main menu (no gameplay)
  Loading = 31                // Loading screen active
  DraggingCorpse = 32         // Dragging a physics body
  HoldingBreath = 33          // Holding breath while hiding
  Injured = 34                // Health ≤ threshold — reduced speed, limp anim, restricted actions
  Staggered = 35              // Transient — hit reaction / stagger
  KnockedDown = 36            // Knocked to ground
  Exhausted = 37              // Stamina fully depleted — panting/immobile
  Panicked = 38               // Stress at catatonic tier — stumble/freeze
  PushingObject = 39          // Pushing a movable object
  AimingThrowable = 40        // Aiming a throwable item
  ThrowingItem = 41           // Throwing an item (transient)
  UsingConsumable = 42        // Using a consumable (heal, inject, etc.)
  InPhotoMode = 43            // Photo mode active — free cam, no gameplay input
  CustomAction = 44           // Reserved for mod/expansion actions
```

### E_OverlayState — Upper-Body Overlay for GASP AnimBP (18 values)

```text
Enum Name: E_OverlayState
(DisplayName = "Upper Body Overlay State")

Values:
  Empty = 0                   // No overlay — default locomotion
  Flashlight = 1              // Holding flashlight
  Melee_OneHanded = 2         // One-handed melee weapon (knife, baton)
  Melee_TwoHanded = 3         // Two-handed melee weapon (axe, pipe)
  Pistol = 4                  // Pistol / handgun
  Shotgun = 5                 // Shotgun
  Rifle = 6                   // Rifle / carbine
  Shield = 7                  // Shield only
  Shield_And_Melee = 8        // Shield + one-handed melee
  Shield_And_Pistol = 9       // Shield + pistol
  DualWield = 10              // Dual-wielding one-handed weapons
  Inspect = 11                // Inspecting an object
  DualHand_Flashlight_Melee = 12  // Flashlight + melee
  Injured = 13                // Injured arm (lowered, favoring)
  Throwable = 14              // Holding a throwable (grenade, bottle)
  Consumable = 15             // Using consumable (syringe, bandage)
  TwoHanded_Heavy = 16        // Heavy two-handed (sledgehammer, chainsaw)
  EmptyHands_Raised = 17      // Empty hands raised (surrender, blocking bare)
```

### E_ActionRequestResult — Result Enum for RequestStateChange (8 values)

```text
Enum Name: E_ActionRequestResult

Values:
  Permitted = 0               // State change approved
  Blocked_CurrentState = 1    // Blocked because player is in an incompatible state
  Blocked_GamePhase = 2       // Blocked by game phase (menu, cutscene, loading)
  Blocked_Dead = 3            // Player is dead
  Blocked_Stamina = 4         // Insufficient stamina
  Blocked_Equipment = 5       // Missing required equipment
  Blocked_Cooldown = 6        // Action on cooldown
  Blocked_Custom = 7          // Blocked by a custom gating rule
```

### E_PlayerVitalSignals — Vital Signal Enum for HeartRate / Breathing (5 values)

```text
Enum Name: E_PlayerVitalSignals
(DisplayName = "Player Vital Signals")

Values:
  Normal = 0                  // Heart rate 60-80 BPM — resting/exploring
  Elevated = 1                // Heart rate 80-110 BPM — sprinting, light combat
  High = 2                    // Heart rate 110-150 BPM — heavy combat, chased
  Critical = 3                // Heart rate 150+ BPM — near death, terrified
  Erratic = 4                 // Irregular rhythm — panic state, hallucinating
```

---

## 2. Structs

### `S_StateChangeRequest`
| Field | Type | Description |
|-------|------|-------------|
| `RequestedState` | `E_PlayerActionState` | The state the requester wants to enter |
| `RequesterTag` | `GameplayTag` | Identifies which system is requesting |
| `Priority` | `Integer` | 0=Normal, 50=High, 100=Force (bypasses all gating) |
| `OverrideReason` | `Text` | Human-readable reason for override/force |

### `S_StateGatingRule`
| Field | Type | Description |
|-------|------|-------------|
| `ActionTag` | `GameplayTag` | The action being gated |
| `BlockedStates` | `Array<E_PlayerActionState>` | States that block this action |
| `BlockedGamePhases` | `Array<E_GamePhase>` | Game phases that block this |
| `RequiredTags` | `GameplayTagContainer` | Tags the player must have |
| `BlockedTags` | `GameplayTagContainer` | Tags the player must NOT have |
| `BlockReason` | `Text` | Reason shown to player when blocked |
| `Priority` | `Integer` | Rule evaluation order (lower = checked first) |

### `S_ActionPermissionResult`
| Field | Type | Description |
|-------|------|-------------|
| `bPermitted` | `Boolean` | Whether the action is allowed |
| `BlockReason` | `Text` | Why it was blocked (if not permitted) |
| `ResultCode` | `E_ActionRequestResult` | Machine-readable result code |

---

## 3. Variables

### Configuration (Instance Editable, Expose On Spawn)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `GatingRules` | `Array<S_StateGatingRule>` | `DefaultRules` | `State Config` | All gating rules defined in blueprint |
| `DefaultState` | `E_PlayerActionState` | `Idle` | `State Config` | State to initialize on BeginPlay |
| `DefaultOverlay` | `E_OverlayState` | `Empty` | `State Config` | Default overlay state |
| `StateTransitionDelay` | `Float` | `0.0` | `State Config` | Minimum time between state changes (anti-spam) |
| `InjuryHealthThreshold` | `Float` | `0.30` | `State Config` | Health ratio (0-1) below which player becomes Injured |
| `InjurySpeedMultiplier` | `Float` | `0.65` | `State Config` | Walk speed multiplier when injured |
| `HeartRateBase` | `Float` | `70.0` | `State Config` | Base heart rate in BPM when calm |
| `HeartRateMax` | `Float` | `180.0` | `State Config` | Maximum heart rate in BPM |
| `StateGatingTable` | `DA_StateGatingTable` | `None` | `State Config` | External Data Asset for designer-tunable gating rules |
| `bLogStateTransitions` | `Boolean` | `false` | `Debug` | Log all state transitions to console |

### Internal (Private / Protected, No Expose)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentState` | `E_PlayerActionState` | `Idle` | `State` | Current exclusive action state |
| `PreviousState` | `E_PlayerActionState` | `Idle` | `State` | Previous state for transitions |
| `CurrentOverlay` | `E_OverlayState` | `Empty` | `State` | Current upper-body overlay state |
| `LastStateChangeTime` | `Float` | `0.0` | `State` | Game time of last state change |
| `StateHistory` | `Array<E_PlayerActionState>` | `Empty` | `State` | History of last N states for debugging |
| `ForceStack` | `Array<E_PlayerActionState>` | `Empty` | `State` | Stack of forced states for nested overrides |
| `bIsTransitioning` | `Boolean` | `false` | `State` | True during a state transition |
| `PendingRequests` | `Array<S_StateChangeRequest>` | `Empty` | `State` | Queued requests during transition |
| `CachedABPRef` | `ABP_GASP` (soft ref) | `None` | `State` | Cached reference to GASP AnimBP |
| `CurrentActionFlags` | `Map<GameplayTag, Float>` | `Empty` | `State` | Active action tags with duration/timestamp |
| `CurrentHeartRate` | `Float` | `70.0` | `Vital Signs` | Current heart rate in BPM (recalculated each tick, smoothed) |
| `CurrentVitalSignal` | `E_PlayerVitalSignals` | `Normal` | `Vital Signs` | Current vital signal category |
| `bIsInjured` | `Boolean` | `false` | `Vital Signs` | True when health ≤ InjuryHealthThreshold |

---

## 4. Functions

### Public Functions

#### `RequestStateChange` → `E_ActionRequestResult`
- **Description:** The primary entry point. Any system requests to transition the player to a new exclusive action state. State Manager checks all gating rules and returns the result.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `NewState` | `E_PlayerActionState` | The desired new action state |
  | `RequesterTag` | `GameplayTag` | Identifies which system is requesting |
  | `Priority` | `Integer` (default 0) | 0=Normal, 50=High, 100=Force |
- **Blueprint Authority:** Any
- **Flow:**
  1. If Priority == 100 (Force): bypass gating, go directly to state transition
  2. If `bIsTransitioning`: queue the request on `PendingRequests`, return `Blocked_CurrentState`
  3. Check time since `LastStateChangeTime` — if < `StateTransitionDelay`: return `Blocked_Cooldown`
  4. If `CurrentState` == `Dead` and `NewState` != `Idle` (respawn): return `Blocked_Dead`
  5. Fire `OnStateChangeRequested`
  6. Iterate `GatingRules` matching `RequesterTag`:
     - If `NewState` is in `BlockedStates` for any rule → fire `OnStateChangeDenied`, return `Blocked_CurrentState` with `BlockReason`
     - If `CurrentGamePhase` is in `BlockedGamePhases` → return `Blocked_GamePhase`
     - If required tags missing or blocked tags present → return appropriate result
  7. Execute state transition (set PreviousState, set CurrentState, update GASP, fire dispatchers)
  8. Return `Permitted`

#### `ForceStateChange` → `void`
- **Description:** Bypasses ALL gating. Used by death system, cutscene bridge, void space entry, and developer cheats. Pushes current state onto `ForceStack`.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `NewState` | `E_PlayerActionState` | The forced state to enter |
  | `Reason` | `Text` | Human-readable reason for force |
- **Blueprint Authority:** Any (Server-authoritative if multiplayer)
- **Flow:**
  1. If `ForceStack` already has `NewState` at top: return (prevents double-force)
  2. Push `CurrentState` onto `ForceStack`
  3. Fire `OnForceOverridePushed`
  4. Execute state transition with `NewState`
  5. Log reason to console if `bLogStateTransitions`

#### `RestorePreviousState` → `void`
- **Description:** Pops the force stack. Used when exiting cutscenes, void space, or force-override states.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any
- **Flow:**
  1. If `ForceStack` is empty: set to `Idle` or previously known normal state, return
  2. Pop top of `ForceStack`
  3. Fire `OnForceOverridePopped`
  4. Execute state transition to popped state

#### `GetCurrentState` → `E_PlayerActionState`
- **Description:** Pure getter — returns `CurrentState`. No side effects.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `GetPreviousState` → `E_PlayerActionState`
- **Description:** Pure getter — returns `PreviousState`. No side effects.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `IsActionPermitted` → `S_ActionPermissionResult`
- **Description:** Non-mutating query. Does NOT change state. Any system can ask "can I do this right now?"
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `ActionTag` | `GameplayTag` | The action tag to check |
- **Blueprint Authority:** Any (Pure)
- **Flow:**
  1. Find all `GatingRules` where `ActionTag` matches
  2. Check `CurrentState` against `BlockedStates`
  3. Check `GI_GameFramework.CurrentGamePhase` against `BlockedGamePhases`
  4. Check `RequiredTags` and `BlockedTags`
  5. Return `S_ActionPermissionResult` with `bPermitted`, `BlockReason`, `ResultCode`

#### `SetOverlayState` → `void`
- **Description:** Tells GASP AnimBP which upper-body overlay to use. Does NOT change `E_PlayerActionState`. Called by equipment system, weapon system, and inspection.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `Overlay` | `E_OverlayState` | The new overlay state |
- **Blueprint Authority:** Any
- **Flow:**
  1. If `Overlay` == `CurrentOverlay`: return (no-op)
  2. Set `CurrentOverlay` = `Overlay`
  3. If `CachedABPRef` is valid: set `ABP_GASP.OverlayState` = `Overlay`
  4. Fire `OnOverlayStateChanged`
  5. Update visible first-person arm mesh via `BPC_EmbodimentSystem`

#### `CanTransitionTo` → `S_ActionPermissionResult`
- **Description:** Pre-flight check without executing. Used by UI to enable/disable buttons.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `TargetState` | `E_PlayerActionState` | The state to check transition eligibility for |
- **Blueprint Authority:** Any (Pure)

#### `UpdateHeartRate` → `void`
- **Description:** Recalculates heart rate each tick based on current state, stress, stamina, and health. Evaluates `E_PlayerVitalSignals` thresholds. Called automatically from Tick.
- **Parameters:** *(none — reads internal state and bound systems)*
- **Blueprint Authority:** Internal (Tick-driven)
- **Flow:**
  1. Calculate base rate from current state:
     - Idle/Walking/Sitting → `HeartRateBase` (60-80)
     - Jogging → Base + 15
     - Sprinting → Base + 30
     - Combat (any weapon state) → Base + 40
     - Hiding/Peeking → Base + 5 (suppressed by stealth)
     - Panicked → Base + 50
     - Dead → 0
  2. Apply health modifier: (1.0 - HealthRatio) * 20 added
  3. Apply stress modifier: StressRatio * 30 added
  4. Apply stamina modifier: if Exhausted → +25
  5. Clamp to `HeartRateMax`
  6. Smooth interpolation: `CurrentHeartRate = FMath::FInterpTo(CurrentHeartRate, Target, DeltaTime, 3.0)`
  7. Evaluate `E_PlayerVitalSignals` from `CurrentHeartRate`:
     - ≤ 80 → Normal
     - ≤ 110 → Elevated
     - ≤ 150 → High
     - ≤ 180 → Critical
     - If Panicked state OR FearSystem active → Erratic (overrides BPM threshold)
  8. If `CurrentVitalSignal` changed, fire `OnVitalSignalChanged`
  9. Write `CurrentHeartRate` to `ABP_GASP.HeartRate` for breathing animation blend

#### `EvaluateInjuryState` → `void`
- **Description:** Called when health changes. Sets `bIsInjured` flag and auto-transitions to/from `Injured` state if health crosses threshold.
- **Parameters:** *(none — reads BPC_HealthSystem)*
- **Blueprint Authority:** Internal (called by health change binding)
- **Flow:**
  1. Get `HealthRatio` from `BPC_HealthSystem.CurrentHealth / MaxHealth`
  2. If `HealthRatio <= InjuryHealthThreshold` AND `!bIsInjured`:
     - Set `bIsInjured = true`
     - If CurrentState is Idle/Walking/Jogging/Sprinting/Crouching: `RequestStateChange(Injured, Tag.Health)`
     - Apply `InjurySpeedMultiplier` to movement
     - Fire `OnInjuryStateChanged(true, HealthRatio)`
  3. If `HealthRatio > InjuryHealthThreshold` AND `bIsInjured`:
     - Set `bIsInjured = false`
     - If CurrentState == Injured: `RequestStateChange(Idle, Tag.Health)`
     - Restore movement speed
     - Fire `OnInjuryStateChanged(false, HealthRatio)`

#### `IsInCombat` → `Boolean`
- **Description:** Convenience — returns true if `CurrentState` is `SwingMelee`, `AimingFirearm`, `FiringFirearm`, `Reloading`, or `DeployingShield`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `IsMovementBlocked` → `Boolean`
- **Description:** Convenience — returns true if player cannot move at all. True when `CurrentState` is `Dead`, `InCutscene`, `InDialogue`, `InInventory`, `InJournal`, `InPauseMenu`, `InMainMenu`, `UsingObject`, `Sitting`, `KnockedDown`, `Panicked`, `Staggered`, `Exhausted`, `Loading`, `InPhotoMode`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `IsMovementRestricted` → `Boolean`
- **Description:** Convenience — returns true if movement is partially restricted (slowed/impaired but not fully blocked). True when `CurrentState` is `Injured`, `Crouching`, `Crawling`, `HoldingBreath`, `AimingFirearm`, `DeployingShield`, `Hiding`, `Peeking`, `GrabbingObject`, `PushingObject`, `UsingConsumable`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `GetHeartRate` → `Float`
- **Description:** Returns `CurrentHeartRate` in BPM. Used by audio system for heartbeat SFX tempo, by UI for vitals display, by haptic system for controller pulse rate.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `GetVitalSignal` → `E_PlayerVitalSignals`
- **Description:** Returns `CurrentVitalSignal`. Used by atmosphere system (audio tension layers), camera (breathing sway), and narrative system (dialogue conditionals).
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `CanEquipWeapon` → `Boolean`
- **Description:** Checks if weapon equip is permitted in current state. Convenience wrapper around `IsActionPermitted`.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)

#### `RegisterActionFlag` → `void`
- **Description:** Lightweight flag system for transient action permissions. Duration of -1 = permanent until unregistered. Fires `OnActionFlagRegistered`.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `Tag` | `GameplayTag` | The flag tag to register |
  | `Duration` | `Float` (default -1.0) | How long the flag persists (-1 = permanent) |

#### `UnregisterActionFlag` → `void`
- **Description:** Removes a previously registered action flag. Fires `OnActionFlagExpired` if the flag was active.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `Tag` | `GameplayTag` | The flag tag to remove |

#### `HasActionFlag` → `Boolean`
- **Description:** Returns true if the given tag is currently registered as an action flag.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `Tag` | `GameplayTag` | The flag tag to check |
- **Blueprint Authority:** Any (Pure)

---

## 5. Event Dispatchers

| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnStateChanged` | `OldState: E_PlayerActionState`, `NewState: E_PlayerActionState` | `Public` | Fired on any state transition |
| `OnStateChangeRequested` | `Request: S_StateChangeRequest` | `Public` | Fired when request is made (before evaluation) |
| `OnStateChangeDenied` | `Request: S_StateChangeRequest`, `Result: E_ActionRequestResult`, `Reason: Text` | `Public` | Fired when a request is denied |
| `OnOverlayStateChanged` | `OldOverlay: E_OverlayState`, `NewOverlay: E_OverlayState` | `Public` | Fired on overlay change |
| `OnActionFlagRegistered` | `Tag: GameplayTag`, `Duration: Float` | `Public` | Fired when a flag is registered |
| `OnActionFlagExpired` | `Tag: GameplayTag` | `Public` | Fired when a timed flag expires |
| `OnForceOverridePushed` | `ForcedState: E_PlayerActionState`, `PoppedState: E_PlayerActionState` | `Public` | Fired when force stack is pushed |
| `OnForceOverridePopped` | `RestoredState: E_PlayerActionState` | `Public` | Fired when force stack is popped |
| `OnVitalSignalChanged` | `OldSignal: E_PlayerVitalSignals`, `NewSignal: E_PlayerVitalSignals` | `Public` | Fired when vital signal tier changes |
| `OnHeartRateChanged` | `NewBPM: Float` | `Public` | Fired when heart rate changes significantly (±5 BPM) |
| `OnInjuryStateChanged` | `bIsInjured: Boolean`, `HealthRatio: Float` | `Public` | Fired when injury state toggles |

---

## 6. Overridden Events / Custom Events

### Event: `BeginPlay` → `Initialize`
- **Description:** Sets up all bindings, caches references, loads default state, and enables tick.
- **Flow:**
  1. Get owner as Character (validate — log error and disable if invalid)
  2. Cache reference to `ABP_GASP` from character's skeletal mesh
  3. Set `CurrentState` = `DefaultState`, `CurrentOverlay` = `DefaultOverlay`
  4. Populate default `GatingRules` from `DA_StateGatingTable` if assigned
  5. Bind to `GI_GameFramework.OnGamePhaseChanged` (for game-phase-gated rules)
  6. Bind to `BPC_HealthSystem.OnDeath` (auto-call `ForceStateChange(Dead, "Death")`)
  7. Register with `DA_GameTagRegistry` for tag-based queries
  8. Bind to `BPC_HealthSystem.OnHealthChanged` → call `EvaluateInjuryState()`
  9. Bind to `BPC_StressSystem.OnStressTierChanged` → recalculate heart rate
  10. Bind to `BPC_StaminaSystem.OnExhaustionStateChanged` → recalculate heart rate
  11. Enable component tick (required for heart rate smoothing)

### Event: `Tick` → `UpdateHeartRate`
- **Description:** Called every tick. Recalculates heart rate and evaluates vital signals.
- **Flow:** (see `UpdateHeartRate` function flow above)

### Event: `OnComponentDestroyed`
- **Description:** Cleanup — unbind all bound delegates, null cached references.
- **Flow:**
  1. Unbind from `GI_GameFramework.OnGamePhaseChanged`
  2. Unbind from `BPC_HealthSystem.OnDeath` and `OnHealthChanged`
  3. Unbind from `BPC_StressSystem.OnStressTierChanged`
  4. Unbind from `BPC_StaminaSystem.OnExhaustionStateChanged`
  5. Set `CachedABPRef` = `None`

---

## 7. Blueprint Graph Logic Flow

### RequestStateChange Flow

```mermaid
flowchart TD
    A[System calls RequestStateChange] --> B{Priority == 100?}
    B -->|Yes Force| C[ExecuteStateTransition]
    B -->|No| D{bIsTransitioning?}
    D -->|Yes| E[Queue on PendingRequests - return Blocked_CurrentState]
    D -->|No| F{Within StateTransitionDelay?}
    F -->|Yes| G[Return Blocked_Cooldown]
    F -->|No| H{CurrentState == Dead?}
    H -->|Yes and NewState != Idle| I[Return Blocked_Dead]
    H -->|No| J[Fire OnStateChangeRequested]
    J --> K[Evaluate GatingRules for RequesterTag]
    K --> L{NewState in BlockedStates?}
    L -->|Yes| M[Fire OnStateChangeDenied - return Blocked_CurrentState]
    L -->|No| N{GamePhase blocks?}
    N -->|Yes| O[Fire OnStateChangeDenied - return Blocked_GamePhase]
    N -->|No| P{Required tags missing?}
    P -->|Yes| Q[Fire OnStateChangeDenied - return reason]
    P -->|No| R{Blocked tags present?}
    R -->|Yes| Q
    R -->|No| C
    C --> S[PreviousState = CurrentState]
    S --> T[CurrentState = NewState]
    T --> U[Update bIsInAction on GASP]
    U --> V[Update ActionIntensity on GASP]
    V --> W[Fire OnStateChanged - OldState, NewState]
    W --> X[Notify SS_EnhancedInputManager - context change]
    X --> Y[Return Permitted]
```

### Overlay State Pipeline

```mermaid
sequenceDiagram
    participant ES as EquipmentSystem
    participant SM as BPC_StateManager
    participant GASP as ABP_GASP
    participant AP as AnimPose

    ES->>SM: SetOverlayState(Pistol)
    SM->>SM: CurrentOverlay = Pistol
    SM->>GASP: ABP_GASP.OverlayState = Pistol
    GASP->>GASP: Blend upper body to Pistol pose
    GASP->>AP: Apply overlay pose
    SM->>SM: Fire OnOverlayStateChanged
    SM->>ES: Confirm overlay set
```

---

## 8. State Gating Reference Tables

### Complete Action → Blocking State Matrix

| Requested Action (Tag) | Blocked If CurrentState Is | Reason |
|------------------------|----------------------------|--------|
| `Action.Move.Walk` | Dead, InCutscene, InDialogue, KnockedDown, Panicked, Exhausted, Sitting, InInventory, InJournal, InPauseMenu, UsingObject, GrabbingObject, InPhotoMode | Movement disabled |
| `Action.Move.Sprint` | Dead, InCutscene, InDialogue, Crouching, Crawling, Hiding, Peeking, Sitting, KnockedDown, Exhausted, Panicked, Reloading, UsingConsumable, Injured | Sprint requires standing + uninjured |
| `Action.Move.Crouch` | Dead, Vaulting, Climbing, Sliding, InCutscene, InDialogue, Sitting, KnockedDown, Exhausted, Panicked | Cannot crouch during traversal |
| `Action.Move.Crawl` | Dead, Standing required states, Vaulting, Climbing, InCutscene, Sitting, KnockedDown | Must be prone first |
| `Action.Move.Jump` | Dead, Hiding, Peeking, Crouching (maybe), Crawling, InCutscene, InDialogue, Sitting, KnockedDown, Panicked, Exhausted, UsingObject, InInventory, InPauseMenu | Jump blocked |
| `Action.Move.Sit` | Dead, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory, InCutscene, InDialogue | Sit requires neutral standing state |
| `Action.Move.StandUp` | Must be Sitting state | Must be sitting first |
| `Action.Traversal.Vault` | Dead, Hiding, Peeking, InCutscene, InDialogue, Reloading, SwingMelee, FiringFirearm, KnockedDown, Panicked | Traversal requires free hands |
| `Action.Traversal.Climb` | Dead, InCutscene, InDialogue, Reloading, SwingMelee, KnockedDown, Panicked | Climbing requires free hands |
| `Action.Traversal.Slide` | Dead, InCutscene, InDialogue, Reloading, SwingMelee, KnockedDown, Panicked | Slide requires two hands |
| `Action.Hide.Enter` | Dead, InCutscene, InDialogue, SwingMelee, FiringFirearm, Reloading, Vaulting, Climbing, KnockedDown, Panicked, Exhausted, GrabbingObject | Hide requires exposed state |
| `Action.Hide.Peek` | Dead, SwingMelee, FiringFirearm, Reloading | Peek only from hidden |
| `Action.Hide.Exit` | InCutscene, Dead (if not force-exit) | Forced exit allowed |
| `Action.Interact.Hold` | Dead, InCutscene, SwingMelee, FiringFirearm, Reloading, Hiding, Peeking, Vaulting, Climbing, KnockedDown, Panicked, InInventory, InPauseMenu, InDialogue, UsingObject | Interaction requires free state |
| `Action.Interact.Instant` | Dead, InCutscene, KnockedDown, Panicked | Instant interaction only blocked by critical |
| `Action.Inventory.Open` | Dead, InCutscene, Vaulting, Climbing, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, Hiding, GrabbingObject, InDialogue, InPuzzle, InTrialScenario | Inventory blocked during actions |
| `Action.Inventory.Close` | (always permitted — but may not open if blocked) | Close always works |
| `Action.Inventory.Equip` | Dead, InCutscene, Vaulting, Climbing, KnockedDown, Panicked | Equip requires stability |
| `Action.Inventory.UseItem` | Dead, InCutscene, Vaulting, Climbing, KnockedDown, Panicked, Hiding | Use requires stable state |
| `Action.Weapon.Fire` | Dead, Hiding, Peeking, Reloading, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, UsingConsumable, GrabbingObject | Cannot fire in these states |
| `Action.Weapon.Aim` | Dead, Hiding, Peeking, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, GrabbingObject | Cannot aim |
| `Action.Weapon.Reload` | Dead, Hiding, Peeking, Vaulting, Climbing, SwingMelee, InCutscene, InDialogue, KnockedDown, Panicked, InPauseMenu, GrabbingObject | Cannot reload |
| `Action.Weapon.Melee` | Dead, Hiding, Peeking, Reloading, Vaulting, Climbing, InCutscene, InDialogue, KnockedDown, Panicked, InInventory, InPauseMenu, GrabbingObject, UsingConsumable | Cannot melee |
| `Action.Weapon.Block` | Dead, Reloading, Vaulting, Climbing, InCutscene, KnockedDown, Panicked, Hiding, InInventory, InPauseMenu | Cannot block |
| `Action.Dialogue.Start` | Dead, InCutscene, Vaulting, Climbing, SwingMelee, FiringFirearm, Hiding, InInventory, InPauseMenu, Reloading, KnockedDown, Panicked | Dialogue requires neutral state |
| `Action.Dialogue.Choose` | Must be InDialogue state | Must be in dialogue |
| `Action.Cutscene.Play` | (handled by GI_GameFramework game phase, not action state) | Cutscene overrides via Force |
| `Action.Cutscene.Skip` | Must be InCutscene, bCanSkip | Must be in cutscene |
| `Action.HoldBreath` | Hiding state only, not Peeking | Breath hold only while hidden |
| `Action.Grab.PhysicsObject` | Dead, InCutscene, Hiding, Vaulting, Climbing, Reloading, SwingMelee, FiringFirearm, KnockedDown, Panicked, InInventory, InPauseMenu | Grab requires free hands |
| `Action.Puzzle.Use` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked | Puzzle requires focus |
| `Action.Consumable.Use` | Dead, InCutscene, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory | Consumable requires free hands |
| `Action.Pause.Open` | InCutscene, Dead, InMainMenu, Loading | Cannot pause in cutscene/death |
| `Action.Pause.Close` | (always permitted) | Close always works |
| `Action.Journal.Open` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, Reloading, KnockedDown, Panicked, InInventory, InPuzzle, InTrialScenario | Journal requires neutral state |
| `Action.Settings.Open` | InCutscene, Loading | Settings limited during cutscene |
| `Action.MainMenu.Return` | (always permitted) | Return to menu always works |
| `Action.QuestLog.Open` | Same as Journal | Same blocking states |
| `Action.Map.Open` | Dead, InCutscene, Vaulting, Climbing, Hiding, InInventory, InPauseMenu | Map in neutral/explore states |
| `Action.Throwable.Aim` | Dead, Hiding, Peeking, Vaulting, Climbing, InCutscene, InDialogue, Reloading, KnockedDown, Panicked, InInventory, InPauseMenu | Throw requires free hands |
| `Action.Throwable.Throw` | Same as Aim + must be in AimingThrowable state | Must be aiming first |
| `Action.PhotoMode.Enter` | Dead, InCutscene, InDialogue, Vaulting, Climbing, Hiding, SwingMelee, FiringFirearm, KnockedDown, Panicked | Photo mode requires neutral state |
| `Action.PhotoMode.Exit` | Must be InPhotoMode | Must be in photo mode |

### Restricted Movement States

| State | Walk Allowed | Sprint Allowed | Jump Allowed | Crouch Allowed | Camera Freelook | Notes |
|-------|-------------|----------------|-------------|----------------|-----------------|-------|
| `Sitting` | **No** | No | No | No | Yes (head only) | Must StandUp to move |
| `Injured` | **Slowed** (×0.65) | No | No | Yes | Yes | Limp anim. Heals above threshold |
| `Hiding` (Locker/Under) | **No** | No | No | No | No (fixed cam) | Fully enclosed — zero movement |
| `Hiding` (BehindCover) | **No** | No | No | N/A | Limited (peek) | Cannot leave cover position |
| `Hiding` (InShadow) | **Yes** (slow) | No | No | Yes | Yes | Standing in shadow |
| `Hiding` (TallGrass) | **Yes** (crouch-walk) | No | No | Yes | Yes | Crouch-moving through vegetation |
| `Staggered` | **No** | No | No | No | Locked | Forced animation — no input |
| `KnockedDown` | **No** | No | No | No | Locked (ground view) | Must GetUp animation first |
| `Exhausted` | **No** | No | No | No | Yes | Heavy breathing — wait for regen |
| `Panicked` | **Stumble** | No | No | No | Random jerks | Camera shakes |
| `HoldingBreath` | **No** | No | No | No | Yes | Must release breath before moving |
| `InPhotoMode` | **No** (free cam) | No | No | No | Full (free cam) | Gameplay input disabled |

---

## 9. GASP Integration Notes

### What BPC_StateManager READS from GASP

| GASP Variable / Event | How Used |
|----------------------|----------|
| `ABP_GASP.bStrafing` | Read to detect if GASP is in strafing mode — informs combat state eligibility |
| `ABP_GASP.bSprinting` | Read to confirm sprint state when `BPC_MovementStateSystem` reports sprint |
| `ABP_GASP.MovementMode` | Read to cross-validate `BPC_MovementStateSystem.CurrentMovementMode` |
| `ABP_GASP.GroundState` | Read to know if airborne (blocks certain actions) |
| `AnimNotify_StateEnter` | Any GASP notify received via `I_AnimNotifyInterface` is forwarded to listeners |

### What BPC_StateManager WRITES to GASP

| GASP Variable / Event | How Written |
|----------------------|-------------|
| `ABP_GASP.OverlayState` | Set via `SetOverlayState(E_OverlayState)` — direct enum value set on cached AnimBP reference |
| `ABP_GASP.bIsInAction` | Set to `true` when `CurrentState` is any action state (not Idle/Walking/Jogging/Sprinting/Crouching) |
| `ABP_GASP.ActionIntensity` | Float 0.0–1.0 set based on action type: 0.0=Idle, 0.3=Interacting, 0.7=SwingMelee, 1.0=FiringFirearm |
| `AnimNotify_OverlayChanged` | Fired when overlay changes — GASP blends to new upper-body pose |

### What BPC_StateManager NEVER Touches

- **Motion Matching Database** — GASP's motion matching is sacred, never modified
- **Camera Manager** — Camera blends, FOV, and offsets belong to `BPC_CameraStateLayer`
- **GASP's Internal State Machine** — Ground/air transitions, rotation mode, strafing logic
- **Animation Blueprint Graph Logic** — Only sets variables, never modifies graph nodes
- **GASP's Root Motion** — Root motion extraction is GASP's domain

---

## 10. Communication Matrix

| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| Any System | `RequestStateChange()` | `E_PlayerActionState`, `RequesterTag: GameplayTag`, `Priority: Int` |
| Any System | `IsActionPermitted()` | `GameplayTag` → `S_ActionPermissionResult` |
| BPC_StateManager | `ABP_GASP` (Direct variable set) | `OverlayState`, `bIsInAction`, `ActionIntensity` |
| BPC_StateManager | `Dispatcher: OnStateChanged` | `OldState`, `NewState` to all bound listeners |
| BPC_StateManager | `Dispatcher: OnOverlayStateChanged` | `OldOverlay`, `NewOverlay` to `BPC_EmbodimentSystem`, `WBP_HUDController` |
| BPC_StateManager | `Dispatcher: OnStateChangeDenied` | Request, Result, Reason to `WBP_InteractionPromptDisplay`, `WBP_NotificationToast` |
| BPC_StateManager | `SS_EnhancedInputManager` (Direct) | Context change notification when state changes |
| BPC_StateManager | `BPC_MovementStateSystem` (Direct) | Receives `OnMovementModeChanged` to track walking/sprinting |
| BPC_StateManager | `BPC_HealthSystem` (Listener on OnDeath) | Auto-transitions to Dead state |
| BPC_StateManager | `Dispatcher: OnVitalSignalChanged` | `OldSignal`, `NewSignal` to `SS_AudioManager`, `BPC_CameraStateLayer` |
| BPC_StateManager | `Dispatcher: OnHeartRateChanged` | `NewBPM: Float` to `SS_AudioManager`, `WBP_AccessibilityUI` |
| `GI_GameFramework` | `Dispatcher: OnGamePhaseChanged` | Notifies StateManager of phase changes for gating |
| `BPC_CameraStateLayer` | `Dispatcher: OnStateChanged` | Adjusts camera based on state (e.g., combat FOV) |
| `BPC_StressSystem` | `Direct: IsActionPermitted` | Checks if stress effects are currently permitted |
| `WBP_InteractionPromptDisplay` | `Listener on OnStateChanged` | Shows/hides interaction prompts based on state |

---

## 11. Validation / Testing Checklist

- [ ] `RequestStateChange` with valid state returns `Permitted` and fires `OnStateChanged`
- [ ] `RequestStateChange` while Dead (except to Idle) returns `Blocked_Dead`
- [ ] `RequestStateChange` during transition queues and returns `Blocked_CurrentState`
- [ ] `ForceStateChange` bypasses all gating and pushes to ForceStack
- [ ] `RestorePreviousState` pops ForceStack and restores correct state
- [ ] `IsActionPermitted` returns correct `S_ActionPermissionResult` for all 35+ actions
- [ ] `SetOverlayState` correctly writes to `ABP_GASP.OverlayState`
- [ ] Overlay change fires `OnOverlayStateChanged` dispatcher
- [ ] Game phase change (MainMenu → InGame → Cutscene) correctly gates actions
- [ ] Death auto-transitions to Dead state via `BPC_HealthSystem.OnDeath` binding
- [ ] Respawn restores previous state from ForceStack
- [ ] Rapid state change requests are throttled by `StateTransitionDelay`
- [ ] `IsInCombat()` returns true for `SwingMelee`, `AimingFirearm`, `FiringFirearm`, `Reloading`, `DeployingShield`
- [ ] `IsMovementBlocked()` returns true for all blocking states
- [ ] Heart rate correctly interpolates and fires threshold change events
- [ ] Vital signal changes fire `OnVitalSignalChanged`
- [ ] Injury state auto-transitions when health crosses threshold
- [ ] Edge case: `ForceStateChange` called twice with same state — second call is no-op
- [ ] Edge case: `RestorePreviousState` with empty ForceStack defaults to Idle
- [ ] Edge case: State Manager handles owner destruction gracefully (null checks on cached ABP ref)
- [ ] Edge case: `RequestStateChange` with invalid/missing RequesterTag logs warning

---

## 12. Reuse Notes

- BPC_StateManager replaces the need for every system to check every other system's state. Systems call `IsActionPermitted(Tag)` instead of `IsPlayerInState(X) && !IsPlayerInState(Y) && ...`
- New states can be added to `E_PlayerActionState` and `DA_StateGatingTable` without modifying the State Manager blueprint logic.
- The `DA_StateGatingTable` Data Asset allows designers to tune gating rules without opening blueprints.
- Force stack pattern supports nested force overrides (e.g., Death → Cutscene flashback → Respawn).
- GASP variables `bIsInAction` and `ActionIntensity` allow AnimBP to blend differently during action vs exploration without the AnimBP knowing about game logic.
- The action flag system (`RegisterActionFlag`/`UnregisterActionFlag`/`HasActionFlag`) enables lightweight transient permission tracking without formal state transitions.
- `E_PlayerVitalSignals` drives audio heartbeat tempo, camera breathing sway, controller haptics, and accessibility visual heartbeat all from a single source of truth.

---

*Blueprint Spec: BPC_StateManager — Central Nervous System for the Modular Game Framework. Architecture document: [`bpc-statemanager.md`](../../architecture/bpc-statemanager.md)*
