add architecture

This commit is contained in:
2026-05-19 10:16:14 +00:00
parent a92b9f4726
commit 7e34765e61
5 changed files with 1943 additions and 0 deletions

View File

@@ -0,0 +1,943 @@
# BPC_StateManager — Central Nervous System for the Modular Game Framework
**Architecture Document | UE 5.55.7 | GASP Compatible**
---
## 1. Problem Statement
The framework has 129 systems, each maintaining its own state machine with 36+ state-gated actions. Without a central authority, systems must hardcode knowledge of every other system's state to know whether an action is permitted. This creates tight coupling and makes adding new states (e.g., a new traversal type or weapon mode) a cascade-change across dozens of files. **BPC_StateManager** solves this by being the single source of truth for "what can the player do right now?"
---
## 2. Proposed Solution
```mermaid
flowchart TD
A[Any System: Request Action] --> B[BPC_StateManager.RequestStateChange]
B --> C{Check Gating Rules}
C -->|Permitted| D[Set NewState + Fire OnStateChanged]
D --> E[Notify: ABP_GASP via SetOverlayState]
D --> F[Notify: BPC_MovementStateSystem]
D --> G[Notify: SS_EnhancedInputManager - context switch]
D --> H[Notify: All bound listeners]
C -->|Blocked| I[Return false + Reason]
I --> J[Requester handles rejection - show prompt, play denied sound]
```
BPC_StateManager sits on the Player Character. It does NOT control GASP's Motion Matching, Camera Manager, or AnimBP. It communicates with GASP exclusively through overlay states and animation notify events. Every other system queries `IsActionPermitted(Tag)` before executing.
---
## 3. File Structure
```
Content/Framework/Player/
BPC_StateManager.uasset # NEW — Central state authority
Content/Framework/Core/
E_PlayerActionState.uasset # NEW — Exclusive action state enum
E_OverlayState.uasset # NEW — Upper-body overlay state enum
E_ActionRequestResult.uasset # NEW — Result enum for RequestStateChange
S_StateChangeRequest.uasset # NEW — Struct for state change request
S_StateGatingRule.uasset # NEW — Struct for gating rule
DA_StateGatingTable.uasset # NEW — Data Asset: all gating rules
```
---
## 4. Enums
### 4.1 E_PlayerActionState — Exclusive Action States
```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
```
### 4.2 E_PlayerVitalSignals — Vital Signal Enum for HeartRate / Breathing
```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
```
### 4.3 E_OverlayState — Upper-Body Overlay for GASP AnimBP
```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)
```
### 4.4 E_ActionRequestResult
```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
```
---
## 5. Structs
### 5.1 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 |
### 5.2 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) |
### 5.3 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 |
---
## 6. 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 |
| `bLogStateTransitions` | `Boolean` | `false` | `Debug` | Log all state transitions to console |
| `StateTransitionDelay` | `Float` | `0.0` | `State Config` | Minimum time between state changes (anti-spam) |
| `DefaultOverlay` | `E_OverlayState` | `Empty` | `State Config` | Default overlay state |
| `InjuryHealthThreshold` | `Float` | `0.30` | `State Config` | Health ratio (0-1) below which player becomes 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 |
### Internal (Private / Protected)
| 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) |
| `CurrentVitalSignal` | `E_PlayerVitalSignals` | `Normal` | `Vital Signs` | Current vital signal category |
| `bIsInjured` | `Boolean` | `false` | `Vital Signs` | True when health ≤ InjuryHealthThreshold |
| `InjurySpeedMultiplier` | `Float` | `0.65` | `Vital Signs` | Walk speed multiplier when injured |
---
## 7. Functions / Custom Events
### 7.1 RequestStateChange
```
RequestStateChange(NewState: E_PlayerActionState, RequesterTag: GameplayTag, Priority: Integer = 0) → 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.
**Flow:**
1. If Priority == 100 (Force): bypass gating, go directly to step 6
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. Iterate `GatingRules` matching `RequesterTag`:
- If `NewState` is in `BlockedStates` for any rule: return `Blocked_CurrentState` with `BlockReason`
- If `CurrentGamePhase` is in `BlockedGamePhases`: return `Blocked_GamePhase`
- If required tags missing or blocked tags present: return appropriate result
6. Execute state transition (see 7.7)
7. Return `Permitted`
### 7.2 ForceStateChange
```
ForceStateChange(NewState: E_PlayerActionState, Reason: Text) → void
```
**Description:** Bypasses ALL gating. Used by death system, cutscene bridge, void space entry, and developer cheats. Pushes current state onto `ForceStack`.
**Flow:**
1. If `ForceStack` already has `NewState` at top: return (prevents double-force)
2. Push `CurrentState` onto `ForceStack`
3. Execute state transition with `NewState`
4. Log reason to console if `bLogStateTransitions`
### 7.3 RestorePreviousState
```
RestorePreviousState() → void
```
**Description:** Pops the force stack. Used when exiting cutscenes, void space, or force-override states.
**Flow:**
1. If `ForceStack` is empty:
- Set to `Idle` or previously known normal state
- Return
2. Pop top of `ForceStack`
3. Execute state transition to popped state
### 7.4 GetCurrentState
```
GetCurrentState() → E_PlayerActionState
```
Pure getter returns `CurrentState`.
### 7.5 GetPreviousState
```
GetPreviousState() → E_PlayerActionState
```
Pure getter returns `PreviousState`.
### 7.6 IsActionPermitted
```
IsActionPermitted(ActionTag: GameplayTag) → S_ActionPermissionResult
```
**Description:** Non-mutating query. Does NOT change state. Any system can ask "can I do this right now?"
**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`
### 7.7 SetOverlayState
```
SetOverlayState(Overlay: E_OverlayState) → void
```
**Description:** Tells GASP AnimBP which upper-body overlay to use. Does not change `E_PlayerActionState`. Called by equipment system, weapon system, and inspection.
**Flow:**
1. If `Overlay` == `CurrentOverlay`: return
2. Set `CurrentOverlay` = `Overlay`
3. If `CachedABPRef` is valid: set `ABP_GASP.OverlayState` = `Overlay` (via `I_AnimNotifyInterface` or direct variable set)
4. Fire `OnOverlayStateChanged`
5. Update visible first-person arm mesh if applicable (via `BPC_EmbodimentSystem`)
### 7.8 CanTransitionTo
```
CanTransitionTo(TargetState: E_PlayerActionState) → S_ActionPermissionResult
```
**Description:** Pre-flight check without executing. Used by UI to enable/disable buttons.
### 7.9 UpdateHeartRate (Tick-Driven)
```
UpdateHeartRate() → void
```
**Description:** Recalculates heart rate each tick based on current state, stress, stamina, and health. Evaluates `E_PlayerVitalSignals` thresholds. Called automatically from Tick.
**Flow:**
1. Calculate base rate:
- 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
### 7.10 EvaluateInjuryState
```
EvaluateInjuryState() → void
```
**Description:** Called when health changes. Sets `bIsInjured` flag and auto-transitions to/from `Injured` state if health crosses threshold.
**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
3. If `HealthRatio > InjuryHealthThreshold` AND `bIsInjured`:
- Set `bIsInjured = false`
- If CurrentState == Injured: `RequestStateChange(Idle, Tag.Health)`
- Restore movement speed
### 7.11 IsInCombat
```
IsInCombat() → Boolean
```
**Description:** Convenience returns true if `CurrentState` is `SwingMelee`, `AimingFirearm`, `FiringFirearm`, `Reloading`, or `DeployingShield`.
### 7.12 IsMovementBlocked
```
IsMovementBlocked() → Boolean
```
**Description:** Convenience returns true if player cannot move. True when `CurrentState` is `Dead`, `InCutscene`, `InDialogue`, `InInventory`, `InJournal`, `InPauseMenu`, `InMainMenu`, `UsingObject`, `Sitting`, `KnockedDown`, `Panicked`, `Staggered`, `Exhausted`, `Loading`, `InPhotoMode`.
### 7.13 IsMovementRestricted
```
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`.
### 7.14 GetHeartRate
```
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.
### 7.15 GetVitalSignal
```
GetVitalSignal() → E_PlayerVitalSignals
```
**Description:** Returns `CurrentVitalSignal`. Used by atmosphere system (audio tension layers), camera (breathing sway), and narrative system (dialogue conditionals).
### 7.16 CanEquipWeapon
```
CanEquipWeapon() → Boolean
```
**Description:** Checks if weapon equip is permitted in current state.
### 7.17 RegisterActionFlag / UnregisterActionFlag / HasActionFlag
```
RegisterActionFlag(Tag: GameplayTag, Duration: Float = -1.0) → void
UnregisterActionFlag(Tag: GameplayTag) → void
HasActionFlag(Tag: GameplayTag) → Boolean
```
**Description:** Lightweight flag system for transient action permissions. Duration of -1 = permanent until unregistered. Used by systems to mark "I am currently in cooldown" or "I am currently active."
### 7.18 Initialize (BeginPlay override)
**Flow:**
1. Get owner as Character
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)`)
7. Register with `GI_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)
---
## 8. 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 |
---
## 9. State Gating Function Table
### 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 (Enclosed Spaces / Injured)
The following states impose movement restrictions beyond simply blocked/unblocked:
| State | Walk Allowed | Sprint Allowed | Jump Allowed | Crouch Allowed | Camera Freelook | Notes |
|-------|-------------|----------------|-------------|----------------|-----------------|-------|
| `Sitting` | **No** | No | No | No | Yes (head only) | Must StandUp to move. Enter via world interaction point (bench, chair, floor) |
| `Injured` | **Slowed** (×0.65) | No | No | Yes | Yes | Limp animation. Heals above threshold. Sprint/jump blocked |
| `Hiding` (Locker/Under) | **No** | No | No | No | No (fixed cam) | Fully enclosed zero movement |
| `Hiding` (BehindCover) | **No** | No | No | N/A (already crouched) | Limited (peek) | Can peek but cannot leave cover position |
| `Hiding` (InShadow) | **Yes** (slow) | No | No | Yes | Yes | Standing in shadow can move but sprint blocked |
| `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 must wait for stamina regen |
| `Panicked` | **Stumble** | No | No | No | Random jerks | Camera shakes, movement impaired |
| `HoldingBreath` | **No** | No | No | No | Yes | Must release breath before moving |
| `InPhotoMode` | **No** (free cam only) | No | No | No | Full (free cam) | Gameplay input disabled, camera only |
---
## 10. GASP Integration Notes
### 10.1 What the State Manager 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 |
### 10.2 What the State Manager 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.01.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 |
### 10.3 What the State Manager 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.4 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
```
---
## 11. Blueprint Flow Diagram — RequestStateChange
```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]
```
---
## 12. 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 |
| `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 |
---
## 13. 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
- [ ] 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)
---
## 14. 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.
---
## Part B — Chooser Table Candidate Audit
### Complete Chooser Table Candidate Catalog (32 identified)
| # | Chooser Table Name | Input Columns (Name + Type) | Output Type | Row Logic Summary | Used By |
|---|-------------------|----------------------------|-------------|-------------------|---------|
| 1 | **Damage Resistance Lookup** | `E_DamageType` (enum), `bIsStackable` (bool) | `Float` (multiplier) | Map DamageType resistance multiplier; accumulate if stackable | `BPC_HealthSystem` (08) |
| 2 | **Exhaustion Tier** | `CurrentStamina/MaxStamina` (float ratio) | `E_ExhaustionState` | ExhaustedThreshold Exhausted; LowThreshold Low; else Normal | `BPC_StaminaSystem` (09) |
| 3 | **Stress Tier** | `CurrentStress` (float) | `E_StressTier` | Descending threshold check: highest exceeded assign tier | `BPC_StressSystem` (10) |
| 4 | **Movement Settings** | `E_MovementMode` (enum) | `S_MovementSettings` | Map[Mode] speed/accel/friction/stamina drain | `BPC_MovementStateSystem` (11) |
| 5 | **Footstep Profile** | `EPhysicalSurface` (enum), `CurrentSpeed` (float vs threshold) | `S_FootstepProfile` (SoundSoft/SoundHard) | Trace surface lookup profile compare velocity to threshold | `BPC_MovementStateSystem` (11) |
| 6 | **Stance Transition** | `E_StanceTransition` (enum) | `Float` (blend time) | Instant0s, SmoothTransitionBlendTime, Forced0s + impulse | `BPC_MovementStateSystem` (11) |
| 7 | **Hide Detectability** | `E_HideType` (enum), `Distance` (float), `LOS trace result` | `Boolean` (detectable) | Not hiddentrue; trace hits cover firstfalse; hits playertrue | `BPC_HidingSystem` (12) |
| 8 | **Hide Spot Quality** | `E_HideSpotQuality` (enum) | `DetectionRangeModifier`, `DetectionSpeedModifier` (floats 0-1) | Better quality lower detection modifiers | `I_HidingSpot` / `BP_HidingSpotBase` (17) |
| 9 | **Door Interaction Outcome** | `EDoorState` (enum), `ELockState` (enum), `bHasKey` (bool) | `E_DoorActionResult` | Lockedcheck key; Closedopen; Openclose | `BP_DoorActor` (19) |
| 10 | **Traversal Type** | `ObstacleHeight` (float) vs thresholds (Low/Medium/High) | `E_TraversalType` | Lowstep; Mediumvault; Highmantle; >High→climb | `BPC_ContextualTraversalSystem` (21) |
| 11 | **Container Fill Method** | `E_ContainerFillMethod` (enum) | `LootTable` or `FixedList` or `Empty` | Switch on FillMethod → generate appropriate loot | `BPC_ContainerInventory` (24) |
| 12 | **Weighted Loot Selection** | `Weight` (float per entry), `bGuaranteed` (bool) | `DA_ItemData` + `quantity` | Roll random vs weight sum; guaranteed items always spawn | `BPC_ContainerInventory` (24) |
| 13 | **Pickup Visual State** | `Distance/Overlap` (enum state) | `E_PickupState` | Far→Idle; Near→Highlighted; Interact→BeingPickedUp; Timer→Respawning | `BP_ItemPickup` (25) |
| 14 | **Equip Result** | `SlotOccupied` (bool), `Category` (tag), `StateBlocked` (bool) | `E_EquipResult` | Cascading validation: slot→category→state→Success | `BPC_EquipmentSlotSystem` (30) |
| 15 | **Equip Animation** | `E_EquipAnimationType` (enum) | `UAnimMontage` | Instant→none; Holster→holster; Equip→equip; Unequip→reverse equip | `BPC_EquipmentSlotSystem` (30) |
| 16 | **Inventory Sort Mode** | `E_SortMode` (enum) | Reordered `Array<S_InventorySlot>` | Switch: ByName, ByCategory, ByRarity, ByWeight, ByQuantity, ByRecent | `BPC_InventorySystem` (31) |
| 17 | **Add Item Outcome** | `HasSpace` (bool), `HasStack` (bool), `WeightCapacity` (float) | `E_AddItemResult` | Find stack→increment; else find slot→new; else→Full/Overweight | `BPC_InventorySystem` (31) |
| 18 | **Alt Death Space** | `GameplayTag` (context) | `TSoftObjectPtr<UWorld>` | Map lookup by context tag → level reference | `BPC_AltDeathSpaceSystem` (38) |
| 19 | **Death Outcome** | `S_DeathOutcomeRules` (struct), `S_DeathContext` (struct) | `E_DeathOutcome` | Check: max deaths→GameOver; has alt space→AltDeathSpace; else→StandardRespawn | `BPC_DeathHandlingSystem` (39) |
| 20 | **Document Font** | `E_DocumentFontStyle` (enum) | `UFont` + color | Handwritten/Typewritten/Digital/BloodScrawled → font asset + color tint | `WBP_JournalDocumentViewer` (50) |
| 21 | **Narrative Flag Query** | `Array<GameplayTag>`, `bUseAndLogic` (bool) | `Boolean` | AND query: all must be true; OR query: any must be true | `BPC_NarrativeStateSystem` (58) |
| 22 | **Dialogue Choice Filter** | `RequiredFlagTag` (GameplayTag) per choice | Filtered `Array<FDialogueChoice>` | If RequiredFlagTag set AND not in narrative state → hide choice | `BPC_DialogueChoiceSystem` (61) |
| 23 | **Consequence Action** | `EConsequenceActionType` (enum) | System call | Switch: PlayDialogue, UpdateObjective, SetWorldState, TriggerCutscene, GiveItem, etc. | `BPC_BranchingConsequenceSystem` (62) |
| 24 | **Scenario Outcome** | `bAllObjectivesComplete` (bool), `bFailureConditionMet` (bool) | `EScenarioOutcome` | All complete→Success; failure→Failure; else→InProgress | `BPC_TrialScenarioSystem` (63) |
| 25 | **Fire Mode** | `E_FireMode` (enum) | `FireRate`, `AmmoPerShot` | Single→1 shot, long delay; Burst→3 shots, medium; Auto→continuous, short | `BPC_FirearmSystem` (74) |
| 26 | **Reload Type** | `CurrentAmmo` (int) == 0 vs > 0 | `EReloadType` | Ammo=0→EmptyReload montage; else→TacticalReload montage | `BPC_ReloadSystem` (78) |
| 27 | **AI State Transition** | `PerceptionStatus` (enum), `AlertLevel` (float), `HealthRatio` (float) | `E_AIState` | HasTarget+HighAlert→Combat; LostTarget→Search; LowHealth→Flee; else→Patrol | `BPC_AIStateMachine` (83) |
| 28 | **Behaviour Variant** | `DA_BehaviourVariant` (Data Asset entries with weights) | `S_BehaviourVariant` | Weighted random selection from variant set | `BPC_BehaviourVariantSelector` (88) |
| 29 | **Difficulty Scaling** | `PlayerMetrics` (struct) vs thresholds | `DamageMultiplier`, `SpawnRate`, `ResourceAmount` | Metrics-to-difficulty mapping: Easy/Normal/Hard/Adaptive | `BPC_DifficultyManager` (89) |
| 30 | **Memory Drift** | `StressTier` (enum), `RandomRoll` (float) | `E_DriftEffectType` | Random selection weighted by stress tier → Visual/Audio/Dialogue | `BPC_MemoryDriftSystem` (97) |
| 31 | **Pacing Intensity** | `Stress` (float), `CombatTime` (float), `ExploreTime` (float) | `E_IntensityBand` + Music/Spawn settings | Composite score → Low/Medium/High/VeryHigh | `BPC_PacingDirector` (98) |
| 32 | **Rare Event** | `DA_RareEvent` weight table | `DA_RareEvent` entry | Weighted random selection from configured events | `BPC_RareEventSystem` (100) |
### Top 5 Most Impactful Chooser Tables — Full Specs
---
#### TOP 1 — Death Outcome Chooser
**Table Name:** `CT_DeathOutcome`
**Blueprint:** [`BPC_DeathHandlingSystem`](../blueprints/05-saveload/39_BPC_DeathHandlingSystem.md:88)
**Input Columns:**
| Column | Type | Value Range | Source |
|--------|------|-------------|--------|
| `StandardDeathsUsed` | `Integer` | 0MaxStandardDeaths | `BPC_RunHistoryTracker.GetDeathCount()` |
| `MaxStandardDeaths` | `Integer` | 199 | `S_DeathOutcomeRules.MaxStandardDeaths` |
| `AltSpaceEntrancesUsed` | `Integer` | 0MaxAltDeathSpaceEntrances | `BPC_RunHistoryTracker.GetAltSpaceEntranceCount()` |
| `MaxAltDeathSpaceEntrances` | `Integer` | 099 | `S_DeathOutcomeRules.MaxAltDeathSpaceEntrances` |
| `bHasAltDeathSpace` | `Boolean` | true/false | `S_DeathContext.bHasAltDeathSpace` |
| `bEnableAltDeathSpace` | `Boolean` | true/false | `S_DeathOutcomeRules.bEnableAltDeathSpace` |
| `bIsBossDeath` | `Boolean` | true/false | `S_DeathContext.KillerTag` matches boss tag |
| `bGameOverOnBossDeath` | `Boolean` | true/false | `S_DeathOutcomeRules.bGameOverOnBossDeath` |
| `bIsNarrativeClimax` | `Boolean` | true/false | `BPC_NarrativeStateSystem.HasFlag(ClimaxFlag)` |
| `bGameOverInNarrativeClimax` | `Boolean` | true/false | `S_DeathOutcomeRules.bGameOverInNarrativeClimax` |
**Output Column:** `E_DeathOutcome` (StandardRespawn / GameOver / AltDeathSpace)
**All Rows:**
| Row | Conditions | Outcome |
|-----|-----------|---------|
| 1 | `bIsBossDeath == true AND bGameOverOnBossDeath == true` | `GameOver` |
| 2 | `bIsNarrativeClimax == true AND bGameOverInNarrativeClimax == true` | `GameOver` |
| 3 | `StandardDeathsUsed >= MaxStandardDeaths` | `GameOver` |
| 4 | `AltSpaceEntrancesUsed >= MaxAltDeathSpaceEntrances AND !bHasAltDeathSpace` | `GameOver` |
| 5 | `bEnableAltDeathSpace == true AND bHasAltDeathSpace == true AND AltSpaceEntrancesUsed < MaxAltDeathSpaceEntrances` | `AltDeathSpace` |
| 6 | (default — none of above) | `StandardRespawn` |
**Reuse Notes:** Rules are externalized to `S_DeathOutcomeRules`, configurable per level/chapter. The chooser evaluates rows in priority order (1→6). First match wins.
---
#### TOP 2 — Traversal Type Chooser
**Table Name:** `CT_TraversalType`
**Blueprint:** [`BPC_ContextualTraversalSystem`](../blueprints/03-interaction/21_BPC_ContextualTraversalSystem.md:224)
**Input Columns:**
| Column | Type | Value Range | Source |
|--------|------|-------------|--------|
| `ObstacleHeight` | `Float` | 0400 cm | Line trace from player location upward |
| `ObstacleDepth` | `Float` | 0200 cm | Line trace from player location forward |
| `ClearanceAbove` | `Float` | 0300 cm | Ceiling check trace above obstacle |
| `bIsGrounded` | `Boolean` | true/false | `BPC_MovementStateSystem.bIsOnGround` |
| `CurrentSpeed` | `Float` | 0SprintSpeed | `BPC_MovementStateSystem.CurrentSpeed` |
**Output Column:** `E_TraversalType` (Step / Vault / Mantle / Slide / Squeeze / Climb / Blocked)
**All Rows:**
| Row | Conditions | Outcome |
|-----|-----------|---------|
| 1 | `!bIsGrounded` | `Blocked` (cannot traverse in air) |
| 2 | `ObstacleHeight <= 15` | `Step` |
| 3 | `ObstacleHeight <= 60 AND CurrentSpeed >= JogSpeed` | `Vault` |
| 4 | `ObstacleHeight <= 150 AND ClearanceAbove >= 180` | `Mantle` |
| 5 | `ObstacleHeight <= 80 AND ClearanceAbove <= 90 AND ClearanceAbove > 40` | `Slide` |
| 6 | `ObstacleDepth >= 60 AND ClearanceAbove <= 80 AND ClearanceAbove >= 30` | `Squeeze` |
| 7 | `ObstacleHeight > 150 AND ClearanceAbove >= 200` | `Climb` |
| 8 | (default) | `Blocked` |
**Reuse Notes:** Thresholds should be in a `DA_TraversalConfig` Data Asset for easy tuning. Root motion montages selected by traversal type from a map: `Map<E_TraversalType, UAnimMontage>`. Motion Warping targets set from obstacle edge location.
---
#### TOP 3 — AI State Transition Chooser
**Table Name:** `CT_AIState`
**Blueprint:** [`BPC_AIStateMachine`](../blueprints/09-ai/83_BPC_AIStateMachine.md:241)
**Input Columns:**
| Column | Type | Value Range | Source |
|--------|------|-------------|--------|
| `CurrentState` | `E_AIState` | Patrol/Search/Combat/Flee/Idle | Self |
| `bHasTarget` | `Boolean` | true/false | `BPC_AIPerceptionSystem.HasCurrentTarget()` |
| `bTargetVisible` | `Boolean` | true/false | `BPC_AIPerceptionSystem.IsTargetVisible()` |
| `AlertLevel` | `Float` | 0.01.0 | `BPC_AlertSystem.AlertLevel` |
| `HealthRatio` | `Float` | 0.01.0 | `BPC_HealthSystem (enemy).CurrentHealth/MaxHealth` |
| `bFearActive` | `Boolean` | true/false | `BPC_FearSystem.IsFeared()` |
| `LastKnownTargetLocation` | `Vector` | world position | `BPC_AIMemorySystem.LastKnownLocation` |
| `DistanceToTarget` | `Float` | 0MaxPerceptionRange | Distance calc |
| `TimeSinceLastSighting` | `Float` | 0∞ seconds | `BPC_AIMemorySystem.TimeSinceLastSighting` |
**Output Column:** `E_AIState` (new target state)
**All Rows:**
| Row | Conditions | Outcome |
|-----|-----------|---------|
| 1 | `bFearActive == true AND HealthRatio <= 0.3` | `Flee` |
| 2 | `CurrentState == Combat AND HealthRatio <= 0.15` | `Flee` |
| 3 | `bHasTarget == true AND bTargetVisible == true AND AlertLevel >= 0.7` | `Combat` |
| 4 | `bHasTarget == true AND bTargetVisible == true AND AlertLevel < 0.7 AND AlertLevel >= 0.3` | `Search` (advance to last known) |
| 5 | `bHasTarget == false AND AlertLevel >= 0.5 AND TimeSinceLastSighting <= 15` | `Search` |
| 6 | `AlertLevel < 0.3 AND TimeSinceLastSighting > 30` | `Patrol` |
| 7 | (default — same as current) | `CurrentState` |
**Reuse Notes:** Transition animations (alert→combat raise weapon, combat→search lower weapon) triggered by state change. BlendSpace selection per state: Patrol uses slow walk, Search uses jog, Combat uses strafing blendspace, Flee uses sprint.
---
#### TOP 4 — Dialogue Choice Filter Chooser
**Table Name:** `CT_DialogueChoices`
**Blueprint:** [`BPC_DialogueChoiceSystem`](../blueprints/07-narrative/61_BPC_DialogueChoiceSystem.md:236)
**Input Columns:**
| Column | Type | Value Range | Source |
|--------|------|-------------|--------|
| `Choice.RequiredFlagTag` | `GameplayTag` | any valid narrative tag | `DA_DialogueData.ChoiceDefinitions[i].RequiredFlag` |
| `Choice.BlockedFlagTag` | `GameplayTag` | any valid narrative tag | `DA_DialogueData.ChoiceDefinitions[i].BlockedFlag` |
| `Choice.RequiredItemTag` | `GameplayTag` | any valid item tag | `DA_DialogueData.ChoiceDefinitions[i].RequiredItem` |
| `Choice.MinStressTier` | `E_StressTier` | CalmCatatonic | `DA_DialogueData.ChoiceDefinitions[i].MinStressTier` |
| `CurrentNarrativeFlags` | `GameplayTagContainer` | accumulated flags | `BPC_NarrativeStateSystem.GetActiveFlags()` |
| `CurrentInventoryTags` | `GameplayTagContainer` | possessed item tags | `BPC_InventorySystem.GetAllItemTags()` |
| `CurrentStressTier` | `E_StressTier` | CalmCatatonic | `BPC_StressSystem.CurrentTier` |
**Output Column:** Filtered `Array<S_DialogueChoice>` (visible choices)
**All Rows (per choice):**
| Row | Conditions | Action |
|-----|-----------|--------|
| 1 | `RequiredFlagTag.IsValid() AND !CurrentNarrativeFlags.HasTag(RequiredFlagTag)` | HIDE choice |
| 2 | `BlockedFlagTag.IsValid() AND CurrentNarrativeFlags.HasTag(BlockedFlagTag)` | HIDE choice |
| 3 | `RequiredItemTag.IsValid() AND !CurrentInventoryTags.HasTag(RequiredItemTag)` | HIDE choice |
| 4 | `MinStressTier > CurrentStressTier` | HIDE choice |
| 5 | (all conditions pass) | SHOW choice |
**Reuse Notes:** Maximum 6 visible choices at once. If more than 6 pass, prioritize by `Priority` field in choice definition. Hidden choices still count for narrative branching — they were "considered" but not presented.
---
#### TOP 5 — Difficulty Scaling Chooser
**Table Name:** `CT_DifficultyScaling`
**Blueprint:** [`BPC_DifficultyManager`](../blueprints/10-adaptive/89_BPC_DifficultyManager.md:243)
**Input Columns:**
| Column | Type | Value Range | Source |
|--------|------|-------------|--------|
| `PlayerDeathCount` | `Integer` | 0∞ | `BPC_PlayerMetricsTracker.DeathCount` |
| `PlayerAccuracy` | `Float` | 0.01.0 | `BPC_PlayerMetricsTracker.HitAccuracy` |
| `AverageCombatTime` | `Float` | 0∞ seconds | `BPC_PlayerMetricsTracker.AvgCombatDuration` |
| `HealthItemUsageRate` | `Float` | 0∞ per hour | `BPC_PlayerMetricsTracker.ConsumableUsageRate` |
| `PlaystyleArchetype` | `E_PlaystyleArchetype` | Aggressive/Stealthy/Explorer/Balanced | `BPC_PlaystyleClassifier.CurrentArchetype` |
| `bIsBossCombat` | `Boolean` | true/false | `BPC_AlertSystem.IsBossCombat()` |
| `CurrentDifficulty` | `E_DifficultyLevel` | Easy/Normal/Hard/Nightmare | Self (current state) |
| `bAdaptiveEnabled` | `Boolean` | true/false | Config setting |
**Output Column Parameters:**
| Output | Type | Range |
|--------|------|-------|
| `EnemyDamageMultiplier` | `Float` | 0.53.0 |
| `EnemyHealthMultiplier` | `Float` | 0.53.0 |
| `SpawnGroupSize` | `Integer` | 18 |
| `ResourceScarcity` | `Float` | 0.22.0 (ammo/health items) |
| `EnemyPerceptionSpeed` | `Float` | 0.52.0 |
| `EnemyReactionTime` | `Float` | 0.22.0 seconds |
**All Rows (simplified — full table uses continuous interpolation):**
| Row | Conditions | Difficulty Band | Multipliers |
|-----|-----------|-----------------|-------------|
| 1 | `bIsBossCombat == true` | Fixed (no adaptive) | Damage=1.2, Health=1.5, Resources=1.0 |
| 2 | `PlayerDeathCount >= 5 AND bAdaptiveEnabled` | Easier | Damage=0.7, Health=0.8, Resources=1.5, Perception=0.7 |
| 3 | `PlayerDeathCount <= 1 AND PlayerAccuracy >= 0.7 AND bAdaptiveEnabled` | Harder | Damage=1.5, Health=1.3, Resources=0.7, Perception=1.3 |
| 4 | `PlaystyleArchetype == Stealthy` | Stealth-tuned | Perception=0.8, SpawnGroups=-2, ReactionTime=1.5 |
| 5 | `PlaystyleArchetype == Aggressive` | Combat-tuned | SpawnGroups=+2, Resources=1.3, Perception=1.0 |
| 6 | (default — Normal metrics) | Normal | All multipliers = 1.0 |
**Reuse Notes:** Continuous interpolation between rows rather than hard switches — avoids jarring difficulty changes. All thresholds and multipliers in `DA_DifficultyProfile` Data Asset. `PlaystyleArchetype` is a rolling average, not instant — prevents oscillation.
---
## 15. Gating Rules Data Asset Specification
### DA_StateGatingTable
```
Class: UPrimaryDataAsset
Asset Path: Content/Framework/Core/DA_StateGatingTable
Variables:
GatingRules: Array<S_StateGatingRule>
- ActionTag: GameplayTag // e.g., "Action.Weapon.Fire"
- BlockedStates: Array<E_PlayerActionState> // e.g., [Dead, Hiding, Reloading, ...]
- BlockedGamePhases: Array<E_GamePhase> // e.g., [MainMenu, Cutscene, Loading]
- RequiredTags: GameplayTagContainer // e.g., [State.CanFire, Status.Armed]
- BlockedTags: GameplayTagContainer // e.g., [Status.Disarmed, Status.Cuffed]
- BlockReason: Text // e.g., "Cannot fire while reloading"
- Priority: Integer // 0 = checked first
```
This Data Asset allows designers to add, remove, or modify gating rules without touching the State Manager blueprint. The blueprint reads the array and evaluates rules in priority order.
---
*Architecture Document: BPC_StateManager. Conforms to the Modular Game Framework conventions. GASP is sacred. Ready for Code agent implementation.*