53 KiB
BPC_StateManager — Central Nervous System for the Modular Game Framework
Architecture Document | UE 5.5–5.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
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
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
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
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
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:
- If Priority == 100 (Force): bypass gating, go directly to step 6
- If
bIsTransitioning: queue the request onPendingRequests, returnBlocked_CurrentState - Check time since
LastStateChangeTime— if <StateTransitionDelay: returnBlocked_Cooldown - If
CurrentState==DeadandNewState!=Idle(respawn): returnBlocked_Dead - Iterate
GatingRulesmatchingRequesterTag:- If
NewStateis inBlockedStatesfor any rule: returnBlocked_CurrentStatewithBlockReason - If
CurrentGamePhaseis inBlockedGamePhases: returnBlocked_GamePhase - If required tags missing or blocked tags present: return appropriate result
- If
- Execute state transition (see 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:
- If
ForceStackalready hasNewStateat top: return (prevents double-force) - Push
CurrentStateontoForceStack - Execute state transition with
NewState - 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:
- If
ForceStackis empty:- Set to
Idleor previously known normal state - Return
- Set to
- Pop top of
ForceStack - 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:
- Find all
GatingRuleswhereActionTagmatches - Check
CurrentStateagainstBlockedStates - Check
GI_GameFramework.CurrentGamePhaseagainstBlockedGamePhases - Check
RequiredTagsandBlockedTags - Return
S_ActionPermissionResultwithbPermitted,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:
- If
Overlay==CurrentOverlay: return - Set
CurrentOverlay=Overlay - If
CachedABPRefis valid: setABP_GASP.OverlayState=Overlay(viaI_AnimNotifyInterfaceor direct variable set) - Fire
OnOverlayStateChanged - 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:
- 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
- Idle/Walking/Sitting →
- Apply health modifier: (1.0 - HealthRatio) * 20 added
- Apply stress modifier: StressRatio * 30 added
- Apply stamina modifier: if Exhausted → +25
- Clamp to
HeartRateMax - Smooth interpolation:
CurrentHeartRate = FMath::FInterpTo(CurrentHeartRate, Target, DeltaTime, 3.0) - Evaluate
E_PlayerVitalSignalsfromCurrentHeartRate:- ≤ 80 → Normal
- ≤ 110 → Elevated
- ≤ 150 → High
- ≤ 180 → Critical
- If Panicked state OR FearSystem active → Erratic (overrides BPM threshold)
- If
CurrentVitalSignalchanged, fireOnVitalSignalChanged - Write
CurrentHeartRatetoABP_GASP.HeartRatefor 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:
- Get
HealthRatiofromBPC_HealthSystem.CurrentHealth / MaxHealth - If
HealthRatio <= InjuryHealthThresholdAND!bIsInjured:- Set
bIsInjured = true - If CurrentState is Idle/Walking/Jogging/Sprinting/Crouching:
RequestStateChange(Injured, Tag.Health) - Apply
InjurySpeedMultiplierto movement
- Set
- If
HealthRatio > InjuryHealthThresholdANDbIsInjured:- Set
bIsInjured = false - If CurrentState == Injured:
RequestStateChange(Idle, Tag.Health) - Restore movement speed
- Set
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:
- Get owner as Character
- Cache reference to
ABP_GASPfrom character's skeletal mesh - Set
CurrentState=DefaultState,CurrentOverlay=DefaultOverlay - Populate default
GatingRulesfromDA_StateGatingTableif assigned - Bind to
GI_GameFramework.OnGamePhaseChanged(for game-phase-gated rules) - Bind to
BPC_HealthSystem.OnDeath(auto-callForceStateChange(Dead)) - Register with
GI_GameTagRegistryfor tag-based queries - Bind to
BPC_HealthSystem.OnHealthChanged→ callEvaluateInjuryState() - Bind to
BPC_StressSystem.OnStressTierChanged→ recalculate heart rate - Bind to
BPC_StaminaSystem.OnExhaustionStateChanged→ recalculate heart rate - 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.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 |
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
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
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
RequestStateChangewith valid state returnsPermittedand firesOnStateChangedRequestStateChangewhile Dead (except to Idle) returnsBlocked_DeadRequestStateChangeduring transition queues and returnsBlocked_CurrentStateForceStateChangebypasses all gating and pushes to ForceStackRestorePreviousStatepops ForceStack and restores correct stateIsActionPermittedreturns correctS_ActionPermissionResultfor all 35+ actionsSetOverlayStatecorrectly writes toABP_GASP.OverlayState- Overlay change fires
OnOverlayStateChangeddispatcher - Game phase change (MainMenu → InGame → Cutscene) correctly gates actions
- Death auto-transitions to Dead state via
BPC_HealthSystem.OnDeathbinding - Respawn restores previous state from ForceStack
- Rapid state change requests are throttled by
StateTransitionDelay IsInCombat()returns true forSwingMelee,AimingFirearm,FiringFirearm,Reloading,DeployingShieldIsMovementBlocked()returns true for all blocking states- Edge case:
ForceStateChangecalled twice with same state — second call is no-op - Edge case:
RestorePreviousStatewith 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 ofIsPlayerInState(X) && !IsPlayerInState(Y) && ... - New states can be added to
E_PlayerActionStateandDA_StateGatingTablewithout modifying the State Manager blueprint logic. - The
DA_StateGatingTableData 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
bIsInActionandActionIntensityallow 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) |
Instant→0s, Smooth→TransitionBlendTime, Forced→0s + impulse | BPC_MovementStateSystem (11) |
| 7 | Hide Detectability | E_HideType (enum), Distance (float), LOS trace result |
Boolean (detectable) |
Not hidden→true; trace hits cover first→false; hits player→true | 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 |
Locked→check key; Closed→open; Open→close | BP_DoorActor (19) |
| 10 | Traversal Type | ObstacleHeight (float) vs thresholds (Low/Medium/High) |
E_TraversalType |
≤Low→step; ≤Medium→vault; ≤High→mantle; >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
Input Columns:
| Column | Type | Value Range | Source |
|---|---|---|---|
StandardDeathsUsed |
Integer |
0–MaxStandardDeaths | BPC_RunHistoryTracker.GetDeathCount() |
MaxStandardDeaths |
Integer |
1–99 | S_DeathOutcomeRules.MaxStandardDeaths |
AltSpaceEntrancesUsed |
Integer |
0–MaxAltDeathSpaceEntrances | BPC_RunHistoryTracker.GetAltSpaceEntranceCount() |
MaxAltDeathSpaceEntrances |
Integer |
0–99 | 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
Input Columns:
| Column | Type | Value Range | Source |
|---|---|---|---|
ObstacleHeight |
Float |
0–400 cm | Line trace from player location upward |
ObstacleDepth |
Float |
0–200 cm | Line trace from player location forward |
ClearanceAbove |
Float |
0–300 cm | Ceiling check trace above obstacle |
bIsGrounded |
Boolean |
true/false | BPC_MovementStateSystem.bIsOnGround |
CurrentSpeed |
Float |
0–SprintSpeed | 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
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.0–1.0 | BPC_AlertSystem.AlertLevel |
HealthRatio |
Float |
0.0–1.0 | BPC_HealthSystem (enemy).CurrentHealth/MaxHealth |
bFearActive |
Boolean |
true/false | BPC_FearSystem.IsFeared() |
LastKnownTargetLocation |
Vector |
world position | BPC_AIMemorySystem.LastKnownLocation |
DistanceToTarget |
Float |
0–MaxPerceptionRange | 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
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 |
Calm–Catatonic | DA_DialogueData.ChoiceDefinitions[i].MinStressTier |
CurrentNarrativeFlags |
GameplayTagContainer |
accumulated flags | BPC_NarrativeStateSystem.GetActiveFlags() |
CurrentInventoryTags |
GameplayTagContainer |
possessed item tags | BPC_InventorySystem.GetAllItemTags() |
CurrentStressTier |
E_StressTier |
Calm–Catatonic | 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
Input Columns:
| Column | Type | Value Range | Source |
|---|---|---|---|
PlayerDeathCount |
Integer |
0–∞ | BPC_PlayerMetricsTracker.DeathCount |
PlayerAccuracy |
Float |
0.0–1.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.5–3.0 |
EnemyHealthMultiplier |
Float |
0.5–3.0 |
SpawnGroupSize |
Integer |
1–8 |
ResourceScarcity |
Float |
0.2–2.0 (ammo/health items) |
EnemyPerceptionSpeed |
Float |
0.5–2.0 |
EnemyReactionTime |
Float |
0.2–2.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.