Files
UE5-Modular-Game-Framework/docs/checklists/bpc-statemanager.md
2026-05-19 10:18:28 +00:00

272 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# BPC_StateManager — Implementation Checklist
**Target:** Code Agent (Game Dev mode)
**Architecture Doc:** [`docs/architecture/bpc-statemanager.md`](../architecture/bpc-statemanager.md)
**Parent System:** Player Character (sits alongside BPC_MovementStateSystem, BPC_HealthSystem, etc.)
---
## Pre-Implementation Verification
- [ ] Read [`docs/architecture/bpc-statemanager.md`](../architecture/bpc-statemanager.md) — Sections 47 (Enums, Structs, Variables, Functions)
- [ ] Read [`CONTEXT.md`](../../CONTEXT.md) — Sections "State Management Conventions" and "Animation Notify Contract"
- [ ] Confirm GASP AnimBP (`ABP_GASP`) has variable `OverlayState` (enum), `bIsInAction` (bool), `ActionIntensity` (float 01)
- [ ] Confirm `BPC_MovementStateSystem` (11) fires dispatchers `OnMovementModeChanged`, `OnPostureChanged`, `OnSprintStateChanged`
- [ ] Confirm `BPC_HealthSystem` (08) fires dispatcher `OnDeath` with death context
- [ ] Confirm `GI_GameFramework` (04) has `E_GamePhase` enum and fires `OnGamePhaseChanged`
---
## Phase 1 — Enum & Struct Creation (Core/)
### Step 1.1: Create `E_PlayerActionState` Enum
- [ ] Asset path: `Content/Framework/Core/E_PlayerActionState`
- [ ] 42 values per [Section 4.1](../architecture/bpc-statemanager.md#41-e_playeractionstate--exclusive-action-states)
- [ ] Display names match exactly
### Step 1.2: Create `E_OverlayState` Enum
- [ ] Asset path: `Content/Framework/Core/E_OverlayState`
- [ ] 18 values per [Section 4.2](../architecture/bpc-statemanager.md#42-e_overlaystate--upper-body-overlay-for-gasp-animbp)
- [ ] Display names match exactly
### Step 1.3: Create `E_ActionRequestResult` Enum
- [ ] Asset path: `Content/Framework/Core/E_ActionRequestResult`
- [ ] 7 values per [Section 4.3](../architecture/bpc-statemanager.md#43-e_actionrequestresult)
### Step 1.4: Create Structs
- [ ] `S_StateChangeRequest` — fields per [Section 5.1](../architecture/bpc-statemanager.md#51-s_statechangerequest)
- [ ] `S_StateGatingRule` — fields per [Section 5.2](../architecture/bpc-statemanager.md#52-s_stategatingrule)
- [ ] `S_ActionPermissionResult` — fields per [Section 5.3](../architecture/bpc-statemanager.md#53-s_actionpermissionresult)
### Step 1.5: Create `DA_StateGatingTable` Data Asset
- [ ] Parent class: `PrimaryDataAsset`
- [ ] Asset path: `Content/Framework/Core/DA_StateGatingTable`
- [ ] Variable: `GatingRules: Array<S_StateGatingRule>`
- [ ] Populate with [Section 9 gating rules](../architecture/bpc-statemanager.md#9-state-gating-function-table) (25+ rules)
---
## Phase 2 — BPC_StateManager Component Creation
### Step 2.1: Create Blueprint Component
- [ ] Asset path: `Content/Framework/Player/BPC_StateManager`
- [ ] Parent class: `ActorComponent`
- [ ] Network: Check `bReplicates = true` if multiplayer planned
### Step 2.2: Add Configuration Variables
Per [Section 6 — Configuration Variables](../architecture/bpc-statemanager.md#6-variables):
- [ ] `GatingRules: Array<S_StateGatingRule>` — Expose On Spawn
- [ ] `DefaultState: E_PlayerActionState` — default `Idle`
- [ ] `bLogStateTransitions: Boolean` — default `false`
- [ ] `StateTransitionDelay: Float` — default `0.0`
- [ ] `DefaultOverlay: E_OverlayState` — default `Empty`
### Step 2.3: Add Internal Variables
Per [Section 6 — Internal Variables](../architecture/bpc-statemanager.md#6-variables):
- [ ] `CurrentState: E_PlayerActionState` — default `Idle`
- [ ] `PreviousState: E_PlayerActionState` — default `Idle`
- [ ] `CurrentOverlay: E_OverlayState` — default `Empty`
- [ ] `LastStateChangeTime: Float`
- [ ] `StateHistory: Array<E_PlayerActionState>`
- [ ] `ForceStack: Array<E_PlayerActionState>`
- [ ] `bIsTransitioning: Boolean`
- [ ] `PendingRequests: Array<S_StateChangeRequest>`
- [ ] `CachedABPRef: ABP_GASP` (soft object reference)
- [ ] `CurrentActionFlags: Map<GameplayTag, Float>`
---
## Phase 3 — Event Dispatchers
Create all 8 dispatchers per [Section 8](../architecture/bpc-statemanager.md#8-event-dispatchers):
- [ ] `OnStateChanged``OldState: E_PlayerActionState`, `NewState: E_PlayerActionState`
- [ ] `OnStateChangeRequested``Request: S_StateChangeRequest`
- [ ] `OnStateChangeDenied``Request: S_StateChangeRequest`, `Result: E_ActionRequestResult`, `Reason: Text`
- [ ] `OnOverlayStateChanged``OldOverlay: E_OverlayState`, `NewOverlay: E_OverlayState`
- [ ] `OnActionFlagRegistered``Tag: GameplayTag`, `Duration: Float`
- [ ] `OnActionFlagExpired``Tag: GameplayTag`
- [ ] `OnForceOverridePushed``ForcedState: E_PlayerActionState`, `PoppedState: E_PlayerActionState`
- [ ] `OnForceOverridePopped``RestoredState: E_PlayerActionState`
---
## Phase 4 — Core Functions
### Step 4.1: `BeginPlay` (Override)
Per [Section 7.13](../architecture/bpc-statemanager.md#713-initialize-beginplay-override):
- [ ] Get Owner as Character
- [ ] Cache `ABP_GASP` from character's skeletal mesh `->GetAnimInstance()`
- [ ] Set `CurrentState = DefaultState`, `CurrentOverlay = DefaultOverlay`
- [ ] Load `GatingRules` from `DA_StateGatingTable` if assigned
- [ ] Bind to `GI_GameFramework.OnGamePhaseChanged`
- [ ] Bind to `BPC_HealthSystem.OnDeath` → call `ForceStateChange(Dead, "Death")`
### Step 4.2: `RequestStateChange`
Per [Section 7.1](../architecture/bpc-statemanager.md#71-requeststatechange):
- [ ] **Priority 100 check:** If `Priority == 100` → bypass gating → go to transition
- [ ] **Transition guard:** If `bIsTransitioning` → queue request → return `Blocked_CurrentState`
- [ ] **Cooldown check:** If `GameTime - LastStateChangeTime < StateTransitionDelay` → return `Blocked_Cooldown`
- [ ] **Dead check:** If `CurrentState == Dead` AND `NewState != Idle` → return `Blocked_Dead`
- [ ] **Gating evaluation:** Iterate `GatingRules` matching `RequesterTag`:
- BlockedStates contains NewState → return `Blocked_CurrentState` with reason
- BlockedGamePhases contains CurrentGamePhase → return `Blocked_GamePhase`
- RequiredTags missing or BlockedTags present → return appropriate result
- [ ] **Transition:** Call `ExecuteStateTransition(NewState)`
- [ ] Return `Permitted`
### Step 4.3: `ExecuteStateTransition` (Private)
- [ ] Set `bIsTransitioning = true`
- [ ] `PreviousState = CurrentState`
- [ ] `CurrentState = NewState`
- [ ] `LastStateChangeTime = GameTime`
- [ ] Push `NewState` onto `StateHistory` (max 20 entries)
- [ ] Update GASP: `ABP_GASP.bIsInAction = (NewState in action states)`
- [ ] Update GASP: `ABP_GASP.ActionIntensity` per state mapping
- [ ] Fire `OnStateChanged` dispatcher
- [ ] Notify `SS_EnhancedInputManager` of context change
- [ ] If `bLogStateTransitions` → print transition to log
- [ ] Set `bIsTransitioning = false`
- [ ] Process `PendingRequests` queue
### Step 4.4: `ForceStateChange`
Per [Section 7.2](../architecture/bpc-statemanager.md#72-forcestatechange):
- [ ] If `ForceStack` top == `NewState` → return (no-op)
- [ ] Push `CurrentState` onto `ForceStack`
- [ ] Fire `OnForceOverridePushed(NewState, CurrentState)`
- [ ] Call `ExecuteStateTransition(NewState)`
- [ ] Log reason if `bLogStateTransitions`
### Step 4.5: `RestorePreviousState`
Per [Section 7.3](../architecture/bpc-statemanager.md#73-restorepreviousstate):
- [ ] If `ForceStack` is empty → set `NewState = Idle`
- [ ] Else → Pop `ForceStack``NewState = popped`
- [ ] Fire `OnForceOverridePopped(NewState)`
- [ ] Call `ExecuteStateTransition(NewState)`
### Step 4.6: `IsActionPermitted`
Per [Section 7.6](../architecture/bpc-statemanager.md#76-isactionpermitted):
- [ ] Find all `GatingRules` where `ActionTag` matches
- [ ] Check CurrentState, GamePhase, RequiredTags, BlockedTags
- [ ] Return `S_ActionPermissionResult` with bool, reason, result code
### Step 4.7: `SetOverlayState`
Per [Section 7.7](../architecture/bpc-statemanager.md#77-setoverlaystate):
- [ ] If `Overlay == CurrentOverlay` → return
- [ ] `CurrentOverlay = Overlay`
- [ ] Set `ABP_GASP.OverlayState = Overlay` if cached ref valid
- [ ] Fire `OnOverlayStateChanged`
- [ ] Notify `BPC_EmbodimentSystem` (if on same actor)
### Step 4.8: Convenience Functions
- [ ] `GetCurrentState()` → pure getter
- [ ] `GetPreviousState()` → pure getter
- [ ] `CanTransitionTo(TargetState)` → pre-flight check (calls gating, no transition)
- [ ] `IsInCombat()` → CurrentState in `[SwingMelee, AimingFirearm, FiringFirearm, Reloading, DeployingShield]`
- [ ] `IsMovementBlocked()` → CurrentState in blocking list per Section 7.10
- [ ] `CanEquipWeapon()` → delegates to `IsActionPermitted(Action.Weapon.Equip)`
### Step 4.9: Action Flag Functions
- [ ] `RegisterActionFlag(Tag, Duration)` → add to `CurrentActionFlags` map, start timer if Duration > 0
- [ ] `UnregisterActionFlag(Tag)` → remove from map, clear timer
- [ ] `HasActionFlag(Tag)` → lookup in map
---
## Phase 5 — GASP Integration
Per [Section 10](../architecture/bpc-statemanager.md#10-gasp-integration-notes):
- [ ] **Read from GASP:** Subscribe to `ABP_GASP` dispatchers for `bStrafing`, `bSprinting`, `MovementMode`, `GroundState`
- [ ] **Write to GASP:** Set `OverlayState`, `bIsInAction`, `ActionIntensity` as variables on cached AnimBP ref
- [ ] **Never touch:** Motion Matching database, Camera Manager, GASP internal state machine, root motion
- [ ] **Overlay pipeline:** Verify `SetOverlayState(Pistol)` → blends upper body correctly
---
## Phase 6 — System Integration (Bind Other Systems)
- [ ] **`BPC_HealthSystem` (08):** Bind `OnDeath``ForceStateChange(Dead)`. Bind `OnRespawn``RestorePreviousState()`
- [ ] **`BPC_MovementStateSystem` (11):** `OnMovementModeChanged` sets walking/jogging/sprinting state. `OnPostureChanged` sets crouching/crawling
- [ ] **`BPC_HidingSystem` (12):** `OnHideStateChanged` → Hidden triggers `RequestStateChange(Hiding)`; Exposed triggers restore
- [ ] **`BPC_ContextualTraversalSystem` (21):** Traversal start → `RequestStateChange(Vaulting/Climbing/Sliding)`; end → restore
- [ ] **`BPC_InteractionDetector` (16):** Interaction start → `RequestStateChange(Interacting)`; end → restore
- [ ] **`BPC_MeleeSystem` (76):** StartSwing → `RequestStateChange(SwingMelee)`; OnSwingComplete → restore
- [ ] **`BPC_FirearmSystem` (74):** Fire → `RequestStateChange(FiringFirearm)` (transient, auto-restore)
- [ ] **`BPC_ReloadSystem` (78):** StartReload → `RequestStateChange(Reloading)`; Complete → restore
- [ ] **`BPC_DeathHandlingSystem` (39):** OnPlayerDeath → `ForceStateChange(Dead)`. OnRespawn → `RestorePreviousState()`
- [ ] **`SS_UIManager` (44):** Inventory open → `RequestStateChange(InInventory)`; close → restore. Pause → `InPauseMenu`
- [ ] **`BPC_DialoguePlaybackSystem` (60):** Dialogue start → `RequestStateChange(InDialogue)`; end → restore
- [ ] **`BPC_CutsceneBridge` (64):** PlayCutscene → `ForceStateChange(InCutscene)`. OnCutsceneCompleted → `RestorePreviousState()`
- [ ] **`BPC_AltDeathSpaceSystem` (38):** Enter → `ForceStateChange(InVoidSpace)`. Exit → `RestorePreviousState()`
- [ ] **`SS_EnhancedInputManager` (128):** Bind to `OnStateChanged` → switch `IMC_` contexts based on state
- [ ] **`BPC_CameraStateLayer` (14):** Bind to `OnStateChanged` → adjust FOV/offset per state
---
## Phase 7 — Integration Pattern (Replace Hardcoded State Checks)
For each of the 36+ state-gated actions:
**OLD pattern (remove):**
```
If (PlayerState != Dead && PlayerState != Hiding && PlayerState != Cutscene...)
→ Execute Action
```
**NEW pattern (replace with):**
```
If (BPC_StateManager.IsActionPermitted(ActionTag).bPermitted)
→ Execute Action
Else
→ Show blocked feedback using result.BlockReason
```
### Systems to Update:
- [ ] `BPC_HidingSystem.EnterHideSpot` → check `Action.Hide.Enter`
- [ ] `BPC_FirearmSystem.Fire` → check `Action.Weapon.Fire`
- [ ] `BPC_ReloadSystem.StartReload` → check `Action.Weapon.Reload`
- [ ] `BPC_MeleeSystem.StartSwing` → check `Action.Weapon.Melee`
- [ ] `SS_UIManager.OpenInventory` → check `Action.Inventory.Open`
- [ ] `BPC_InteractionDetector.PerformInteraction` → check `Action.Interact.Hold`
- [ ] `BPC_ContextualTraversalSystem.AttemptTraversal` → check `Action.Traversal.*`
- [ ] `BPC_EquipmentSlotSystem.EquipItem` → check `Action.Inventory.Equip`
- [ ] `BPC_ConsumableSystem.UseConsumable` → check `Action.Consumable.Use`
- [ ] `BPC_PhysicsDragSystem.GrabObject` → check `Action.Grab.PhysicsObject`
- [ ] `BPC_DialoguePlaybackSystem.StartDialogue` → check `Action.Dialogue.Start`
- [ ] `WBP_PauseMenu.OpenPause` → check `Action.Pause.Open`
---
## Phase 8 — Testing
Per [Section 13 Testing Checklist](../architecture/bpc-statemanager.md#13-testing-checklist):
- [ ] Valid state changes return `Permitted`
- [ ] Dead blocks all actions except respawn
- [ ] Force bypasses all gating
- [ ] ForceStack push/pop restores correct state
- [ ] Overlay state correctly writes to GASP
- [ ] Game phase changes gate actions correctly
- [ ] Rapid requests are throttled by `StateTransitionDelay`
- [ ] 25+ action gating rules all work
- [ ] Edge case: double Force same state = no-op
- [ ] Edge case: RestorePreviousState with empty stack = Idle
- [ ] Edge case: Owner destroyed during transition = graceful null check
---
## Files Created (Summary)
| File | Type | Path |
|------|------|------|
| `E_PlayerActionState` | Enum | `Content/Framework/Core/` |
| `E_OverlayState` | Enum | `Content/Framework/Core/` |
| `E_ActionRequestResult` | Enum | `Content/Framework/Core/` |
| `S_StateChangeRequest` | Struct | (Blueprint-internal) |
| `S_StateGatingRule` | Struct | (Blueprint-internal) |
| `S_ActionPermissionResult` | Struct | (Blueprint-internal) |
| `DA_StateGatingTable` | Data Asset | `Content/Framework/Core/` |
| `BPC_StateManager` | Blueprint Component | `Content/Framework/Player/` |
---
*Checklist v1.0 — Aligned with [`docs/architecture/bpc-statemanager.md`](../architecture/bpc-statemanager.md). Ready for Code agent implementation.*