272 lines
14 KiB
Markdown
272 lines
14 KiB
Markdown
# 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 4–7 (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 0–1)
|
||
- [ ] 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.* |