From 0efb54a9f24f08dd7d0c1ee16d585a8dc3ad21fd Mon Sep 17 00:00:00 2001 From: admin Date: Tue, 19 May 2026 11:09:46 +0000 Subject: [PATCH] Add Player State & Embodiment Systems --- Player-State-%26-Embodiment-Systems.md | 456 +++++++++++++++++++++++++ 1 file changed, 456 insertions(+) create mode 100644 Player-State-%26-Embodiment-Systems.md diff --git a/Player-State-%26-Embodiment-Systems.md b/Player-State-%26-Embodiment-Systems.md new file mode 100644 index 0000000..7e63872 --- /dev/null +++ b/Player-State-%26-Embodiment-Systems.md @@ -0,0 +1,456 @@ +# 02 — Player State & Embodiment Systems (Systems 08-15) + +**Category Purpose:** These 8 systems form the player character's core simulation layer — health, stamina, stress, movement, hiding, first-person body, camera, and metrics tracking. They cascade: health affects stress, stress affects movement, movement affects camera. All feed into the animation system (ABP_GASP) via event dispatchers. + +--- + +## System Index + +| # | System | Asset Type | Role | +|---|--------|-----------|------| +| 08 | `BPC_HealthSystem` | Component | Health pool, damage resistance, death trigger, healing | +| 09 | `BPC_StaminaSystem` | Component | Stamina pool, sprint/action drain, exhaustion states | +| 10 | `BPC_StressSystem` | Component | Psychological stress, tiers (Calm→Catatonic), hallucinations | +| 11 | `BPC_MovementStateSystem` | Component | Movement mode + posture tracking; GASP liaison; footsteps | +| 12 | `BPC_HidingSystem` | Component | Hide/peek/breath-hold; LOS checks; stress decay while hidden | +| 13 | `BPC_EmbodimentSystem` | Component | First-person body mesh, arm IK, visibility modes, overlays | +| 14 | `BPC_CameraStateLayer` | Component | Camera FOV/offset/rotation layers for all states; shake system | +| 15 | `BPC_PlayerMetricsTracker` | Component | Player metrics: accuracy, deaths, playstyle ratios | + +--- + +## Category Data Flow — The Cascade + +``` +┌──────────────────────────────────────────────────────────────────────┐ +│ PLAYER SIMULATION CASCADE │ +│ │ +│ Combat Damage │ +│ ↓ │ +│ BPC_HealthSystem │ +│ ↓ dispatchers: OnDamageTaken → │ +│ BPC_StressSystem ←── also fed by: enemy proximity, environment, │ +│ ↓ dispatchers: OnStressTierChanged → narrative events │ +│ BPC_CameraStateLayer (FOV pulse, chromatic aberration) │ +│ BPC_MovementStateSystem (movement penalties at high stress) │ +│ ↓ dispatchers: OnMovementModeChanged → │ +│ BPC_StaminaSystem (regen blocked while sprinting) │ +│ ABP_GASP (animation selection) │ +│ ↓ dispatchers: OnSprintStateChanged → │ +│ BPC_StaminaSystem (continuous drain during sprint) │ +│ │ +│ BPC_HidingSystem ←── interacts with both: │ +│ ↓ stress decay while hidden │ +│ ↓ camera constrained while hiding │ +│ ↓ movement disabled while inside spot │ +│ │ +│ BPC_EmbodimentSystem ←── visual overlays from damage/water │ +│ BPC_PlayerMetricsTracker ←── records everything for analytics │ +└──────────────────────────────────────────────────────────────────────┘ +``` + +--- + +## 08 — BPC_HealthSystem: Health, Damage & Death + +**What It Does:** The central survivability component. Manages a health pool (MaxHealth → CurrentHealth), applies damage with type-based resistance calculation, handles healing, tracks death state (Alive → Dying → Dead → PermaDeath), and fires dispatchers that cascade to stress, UI, and death handling systems. + +**How It Works Internally:** + +**Damage Application Flow:** +1. `ApplyDamage(DamageEvent)` called by weapon, trap, or environment +2. Early-out checks: Is DeathState == Dead? Is bIsInvincible? → return 0 +3. Calculate resistance: look up `S_DamageResistance` for `DamageType`, multiply `BaseAmount` by `Multiplier` +4. `CurrentHealth -= EffectiveDamage` +5. Reset `RegenDelay` timer (regen starts after delay from last damage) +6. Fire `OnDamageTaken` → `BPC_StressSystem` reacts, HUD updates +7. Check thresholds: if `CurrentHealth <= 0` → `HandleDeath()`; if `CurrentHealth <= CriticalHealthPercent * MaxHealth` → fire `OnHealthCritical` +8. If DamageType is `Fear` → stress multiplier on `BPC_StressSystem` + +**Resistance System:** +- Default resistances stored as array of `S_DamageResistance` (type → multiplier) +- 7 damage types: Physical, Arcane, Fire, Poison, Fear, Environmental, True (bypasses all) +- `AddResistance()` / `RemoveResistance()` at runtime for buffs/debuffs + +**Death States:** +- `Alive` → normal; `Dying` → downed, waiting for recovery/final death; `Dead` → no actions; `PermaDeath` → save-scrubbing locked +- On death: clear all timers (regen, DoT, invincibility), fire `OnDeath`, notify `GM_CoreGameMode.HandlePlayerDead()` + +**Health Regeneration:** +- `RegenDelay` seconds after last damage before regen starts +- Timer ticks `RegenRate` HP per second until `CurrentHealth >= MaxHealth` +- Blocked by death state + +**Implementation Patterns:** +- `GetHealthNormalised()` returns 0.0-1.0 for UI bars — smooth interpolation, never raw polling +- `KillInstant()` bypasses everything for scripted deaths +- `ApplyDamageOverTime()` creates a looping timer with unique SourceName to prevent stacking +- `SetMaxHealth(NewMax, bMaintainRatio)` scales CurrentHealth proportionally or clamps +- All dispatchers fire on authority; UI binds and reacts + +**Integration Points:** +- **Listens to:** Nothing directly (called by damagers) +- **Broadcasts:** `OnHealthChanged`, `OnDamageTaken`, `OnHealthCritical`, `OnDeath`, `OnDeathStateChanged`, `OnInvincibilityChanged` +- **Key consumers:** `BPC_StressSystem` (damage → stress), `WBP_HUD` (health bar), `GM_CoreGameMode` (death), `BPC_HitReactionSystem` (stagger) + +**Edge Cases:** +- Invincibility blocks all damage for its duration (used after respawn/dodge) +- DoT with same SourceName replaces existing (no stacking) +- ApplyHealing on dead character returns 0, no dispatchers +- Negative damage treated as 0 + +--- + +## 09 — BPC_StaminaSystem: Stamina Pool & Exhaustion + +**What It Does:** Manages a stamina resource that drains during sprinting, jumping, climbing, dodging, and special attacks. Enforces minimum stamina thresholds per action, applies exhaustion penalties (Normal → Low → Exhausted), and coordinates with MovementState for regen blocking while moving. + +**How It Works Internally:** + +**Drain System:** +- Each `E_StaminaActionType` (Sprint, Jump, Climb, SpecialAttack, Dodge, Environment) has a `S_StaminaDrainRate` config: `DrainPerSecond` (continuous), `DrainFlat` (one-shot), `MinStamina` (threshold to perform), `CooldownAfterUse` +- `DrainStamina(ActionType)` checks: cooldown active? sufficient stamina? → deduct, update exhaustion, start regen delay +- `StartContinuousDrain(ActionType)` for sprint/climb — timer runs at 0.1s intervals +- `StopContinuousDrain(ActionType)` stops when action ends + +**Exhaustion States:** +- `Normal` (full operation) → `Low` (below LowThreshold, heavy breathing) → `Exhausted` (below ExhaustedThreshold, cannot sprint, slow regen) +- `UpdateExhaustionState()` checks `CurrentStamina / MaxStamina` against configured thresholds +- Transition downward is instant; recovery is gradual +- `OnExhausted` dispatcher → player controller shows feedback, sprint blocked + +**Regeneration:** +- `RegenSettings` configures: `RegenRate`, `RegenDelay`, `ExhaustedRegenRate`, `ExhaustedRegenDelay`, `RegenBlockedWhileMoving` +- If `RegenBlockedWhileMoving`: listens to `BPC_MovementStateSystem.OnMovementModeChanged`, pauses regen when speed > walk +- `BlockRegen(Duration)` for external debuffs + +**Implementation Patterns:** +- `CanAffordAction(ActionType)` checks both stamina level and cooldown before allowing +- Per-action cooldowns prevent dodge/jump spamming +- `RestoreStamina(Amount, Tags)` for potions/resting +- `SetMaxStamina(NewMax, bMaintainRatio)` scales current proportionally + +**Integration Points:** +- **Called by:** `PC_PlayerController` (sprint, jump, dodge input) +- **Broadcasts:** `OnStaminaChanged` → `WBP_HUD`, `OnExhausted` → movement/audio, `OnExhaustionStateChanged` → `ABP_GASP` (breathing anim) +- **Listens to:** `BPC_MovementStateSystem.OnMovementModeChanged` (regen blocking) +- **Key consumers:** `BPC_MovementStateSystem` (exhausted blocks sprint), `BPC_CameraStateLayer` (exhaustion effects) + +**Edge Cases:** +- Multiple continuous drains stack independently +- DrainStamina during cooldown returns false without side effects +- MaxStamina change with bMaintainRatio preserves current percentage +- All timers cleaned on component destroy + +--- + +## 10 — BPC_StressSystem: Psychological Stress & Sanity + +**What It Does:** Simulates the player's psychological state via an accumulating stress meter. Multiple stress sources contribute simultaneously (enemy proximity, environmental horror, health deficit, narrative events, supernatural, isolation). Stress tiers (Calm→Uneasy→Distressed→Panicked→Terrified→Catatonic) drive visual distortion, audio hallucinations, movement penalties, and narrative branching. + +**How It Works Internally:** + +**Multi-Source Architecture:** +- `ActiveSources` map (FName identifier → `S_StressSourceData`) tracks all contributing sources +- Each source has: `CurrentIntensity`, `MaxIntensity`, `DecayRate`, `SourceTags` +- `AddStressSource()` adds or updates a source; `RemoveStressSource()` removes it +- `RecalculateFromSources()` sums all source intensities and updates total + +**Stress Events:** +- `AddStress(Event)` can be instant (`bIsInstant`) or gradual (adds to source system) +- `HealthDeficitMultiplier` amplifies incoming stress when health < 50% — creates death spiral feel +- `OnDamageTakenHandler` converts damage amount to stress (Fear damage type gets 2x multiplier) +- `ForcePanic(Duration)` temporarily forces Panicked tier for narrative use + +**Tier System:** +- 6 tiers with configurable thresholds: `Uneasy` (subtle audio), `Distressed` (visual noise, heartbeat), `Panicked` (heavy distortion, movement penalty), `Terrified` (hallucinations, input inversion), `Catatonic` (freeze/stumble) +- `UpdateTier()` compares `CurrentStress` against threshold values +- Tier transitions fire `OnStressTierChanged` — systems react to tier, not raw stress value +- `TierEntryTimes` map records when each tier was entered for duration tracking + +**Hallucination System:** +- At `Terrified` tier: `HandleHallucinationCheck()` rolls random chance each tick +- Triggers random hallucination events (visual, audio, dialogue) +- Stops when tier drops below `Distressed` + +**Safe Zones & Recovery:** +- `SetSafeZone(bSafe)` toggles passive decay +- `PassiveDecayTick()` slowly reduces stress when in safe area or below Distressed +- `OnSafeZoneChanged` dispatcher → atmosphere system adjusts + +**Implementation Patterns:** +- Sources with identifiers enable precise tracking (e.g., "Enemy_Guard_12", "Area_Basement") +- `GetTierDuration(Tier)` for narrative systems to query how long player has been panicked +- At Catatonic, further stress accumulation is blocked (immune at max) +- All effects driven by tier dispatchers, never by polling + +**Integration Points:** +- **Called by:** Enemies (proximity), environment triggers, `BPC_HealthSystem` (damage handler) +- **Broadcasts:** `OnStressTierChanged` → `BPC_CameraStateLayer` (post-process), `ABP_GASP` (anim), `WBP_HUD` (vignette), `OnHallucinationTriggered` → hallucination/audio managers, `OnCatatonicStateEntered` → input lockdown +- **Listens to:** `BPC_HealthSystem.OnDamageTaken` + +**Edge Cases:** +- Catatonic blocks all non-instant stress additions +- Stress clamping prevents overflow past MaxStress +- Source with matching identifier updates existing entry (no duplicate sources) +- ForcePanic temporarily locks tier and restores gradually after duration + +--- + +## 11 — BPC_MovementStateSystem: Movement Mode & Posture + +**What It Does:** The "movement oracle" — tracks the player's current state (movement mode + posture), reports changes via dispatchers, and applies movement settings to the CharacterMovementComponent. Other systems query here instead of polling the movement component directly. Bridges gameplay state to GASP animation. + +**How It Works Internally:** + +**Dual State Tracking:** +- `CurrentMovementMode` (Idle, Walking, Jogging, Sprinting, CrouchWalk, Sneaking) — speed tier +- `CurrentPosture` (Standing, Crouching, Prone, Sliding, Climbing, Vaulting) — body position +- These are independent — you can be Sprinting while Standing, or CrouchWalk while Crouching +- `PreviousMovementMode` and `PreviousPosture` stored for transition detection + +**Movement Settings Application:** +- `MovementSettings` map (Mode → `S_MovementSettings`) defines: `MaxWalkSpeed`, `Acceleration`, `Deceleration`, `GroundFriction`, `bCanSprint`, `bCanCrouch`, `StaminaDrainMultiplier` +- `SetMovementMode(NewMode)` applies the corresponding settings to `CharacterMovementComponent` +- `SetPosture(NewPosture)` adjusts CapsuleComponent half-height (Standing=96, Crouch=60, Prone=30) +- Prone requires going through Crouching first (enforced) + +**Movement Penalties:** +- `ApplyMovementPenalty(Multiplier, Duration, PenaltyTag)` temporarily multiplies MaxWalkSpeed +- Timer restores original value after Duration +- Used by stress system (movement penalty at high stress), injury system, debuffs +- Each penalty tagged for tracking — multiple penalties stack multiplicatively? + +**Footstep System:** +- `CalculateFootstep()`: line trace downward from foot → get surface physical material → look up `S_FootstepProfile` matching surface and mode +- `DefaultFootstepProfile` as fallback +- Fires `OnFootstep` dispatcher with mode and surface type for audio system +- Velocity threshold determines soft vs hard footstep sound + +**GASP Integration:** +- On every mode/posture change: notify ABP_GASP via direct reference or interface +- Sets GASP-specific animation variables (bStrafing, bSprinting, gait, stance) +- This is the ONLY component that talks directly to ABP_GASP for movement state + +**Implementation Patterns:** +- Does NOT handle input — input remains in `PC_PlayerController` +- `CanTransitionToPosture(TargetPosture)` traces for ceiling clearance +- `SetSprinting(bSprinting)` wraps SetMovementMode with stamina awareness +- `OnMovementUpdated(Tick)` checks velocity, detects start/stop, tracks ground state +- `GetCurrentSpeedNormalised()` for smooth animation blend values + +**Integration Points:** +- **Called by:** `PC_PlayerController` (input → mode changes) +- **Broadcasts:** `OnMovementModeChanged`, `OnPostureChanged`, `OnMovementStart/Stop`, `OnSprintStateChanged`, `OnJumped/Landed`, `OnFootstep` +- **Key consumers:** `ABP_GASP` (animation), `BPC_StaminaSystem` (drain/regen), `BPC_CameraStateLayer` (FOV), `BP_AudioManager` (footsteps), `BPC_InteractionDetector` (range scaling) + +**Edge Cases:** +- Forced transitions (knockdown) apply impulse regardless of current state +- Cannot transition to upright if ceiling is too low (traced by CanTransitionToPosture) +- Rapid mode switching is protected by TransitionBlendTime +- Sprinting auto-stops when stamina hits 0 (external listener) + +--- + +## 12 — BPC_HidingSystem: Stealth & Concealment + +**What It Does:** Manages the player entering, occupying, peeking from, and exiting environmental hiding spots. Handles line-of-sight checks against enemies, breath-holding mechanics, and stress reduction while concealed. The central hub for all stealth gameplay. + +**How It Works Internally:** + +**Hide State Machine:** +``` +Exposed → Entering → Hidden ⇄ Peeking → Exiting → Exposed +``` +- `Entering/Exiting`: transient animation states +- `Hidden`: fully concealed, LOS blocked, stress decaying +- `Peeking`: partial exposure, camera offset, timed max duration +- Each transition broadcasts `OnHideStateChanged` + +**Hide Spot Types:** +- `Locker`: fully enclosed (wardrobe, locker) — complete concealment +- `BehindCover`: behind low wall/crate — peeking possible +- `Under`: under bed/table — prone entry +- `InShadow`: standing in shadow volume — dynamic, proximity-based +- `TallGrass`: crouch-moving through vegetation — partial concealment +- Each type stored in `S_HideSpotInfo` from the hiding spot actor + +**Entry Protocol:** +1. Player presses interact near a hide spot +2. `EnterHideSpot(HideSpotActor)` → validate I_HidingSpot interface, check slot availability +3. Set `CurrentHideState = Entering`, disable movement input +4. Play entering animation → animation notify triggers `OnAnimationHideEnterComplete` +5. Set `CurrentHideState = Hidden`, start stress decay timer, start periodic LOS check + +**Line of Sight (LOS) System:** +- `IsPlayerDetectable(EnemyLocation, DetectionRange)` called by AI perception +- If not hidden: always detectable +- If hidden: line trace from enemy to player → if trace hits hide spot actor before player: concealed +- Peeking state increases detection radius +- `PerformLOSCheck()` runs on timer: iterates all enemies in range, checks each +- If any enemy has LOS: fire `OnHideSpotCompromised` → stress spike, forced exit warning + +**Peek System:** +- `StartPeek(Direction)` — Left/Right/Over from behind cover +- Camera moves to peek socket location on hide spot actor +- `MaxPeekDuration` timer forces return to Hidden +- `PeekCooldown` prevents rapid toggling + +**Stress Interaction:** +- `StressDecayWhileHidden` applied per second while in Hidden state +- `bBlocksStress` on hide spot prevents new stress sources from affecting player +- Hide spots double as psychological safe rooms + +**Implementation Patterns:** +- `ForceKickFromHide()` for enemy discovery — bForceExit skips animation +- `TryBreathHold()` reduces noise for 8 seconds near enemies +- `OnDamageWhileHiding` handler: penetrating damage forces exit; non-penetrating converts to stress +- Destroying a hide spot while player is inside force-exits them + +**Integration Points:** +- **Called by:** `PC_PlayerController` (interact input), AI (IsPlayerDetectable queries) +- **Broadcasts:** `OnHideStateChanged`, `OnEntered/ExitedHidingSpot`, `OnPeekStarted/Ended`, `OnHideSpotCompromised`, `OnForcedExitWarning`, `OnBreathHoldChanged` +- **Key consumers:** `BPC_CameraStateLayer` (peek/hide offsets), `BPC_StressSystem` (decay), `ABP_GASP` (hide anims), `AI_EnemyController` (awareness), `WBP_HUD` (hide indicators) + +**Edge Cases:** +- EnterHideSpot while already hiding returns false +- ExitHideSpot while Exposed does nothing +- Multiple players can't occupy limited-slot spots +- Peek max duration and cooldown enforced +- Shutting down the hide spot actor force-exits the player + +--- + +## 13 — BPC_EmbodimentSystem: First-Person Body + +**What It Does:** Creates first-person body awareness — the player sees their arms, shadow, and optionally legs/torso in context-appropriate situations. Manages mesh visibility modes, environmental body overlays (blood, water, mud), wall proximity detection for arm IK, and shadow casting. + +**How It Works Internally:** + +**Visibility Modes:** +- `FullBody` (third-person, mirrors, death) → `ArmsOnly` (default first-person) → `ArmsAndShadow` → `Minimal` (only hands, high stress) → `Hidden` (cutscenes, UI) +- `SetVisibilityMode(NewMode, bInstant)` toggles mesh components: ArmsMesh, BodyShadowComponent, optional TorsoMesh +- `S_BodyPartVisibility` struct derived from mode controls individual part visibility + +**Overlay System:** +- `E_BodyOverlayState`: Clean, BloodSpatter, WaterDroplets, Mud, Toxic +- `ApplyOverlay(Overlay)` with blend: sets MaterialParameter on ArmsMesh toward `TargetValue` at `BlendSpeed` +- Auto-fades after `Duration` seconds +- `ClearOverlay(Type)` removes single or all overlays +- Multiple overlays stack: blood + water = combined effect on material +- `ApplyOverlayBlendTick()` runs each frame for smooth transitions + +**Wall Proximity / Arm IK:** +- `CheckWallProximity()`: line trace from camera forward by `BrushTraceDistance` +- If wall detected: apply IK offset to arms (avoids clipping), set `bIsNearWall = true` +- Fires `OnWallProximityChanged` → ABP_Arms for IK adjustment + +**Damage/Environment Integration:** +- `OnDamageTakenBloodHandler`: blood overlay intensity scales with damage amount +- `OnEnterWaterHandler`: water droplets overlay with fast blend, auto-dries +- Bound to `BPC_HealthSystem.OnDamageTaken` and water volume triggers + +**Implementation Patterns:** +- Mesh references cached at BeginPlay from owner (first-person skeletal mesh) +- Shadow uses separate component with shadow-only material — not full character mesh +- All overlay blending uses Material Parameter Collections for real-time updates +- Wall proximity is simple line trace; advanced IK requires arms animation blueprint + +**Integration Points:** +- **Listens to:** `BPC_HealthSystem.OnDamageTaken` (blood), water volume triggers +- **Broadcasts:** `OnVisibilityModeChanged` → `BPC_CameraStateLayer` (FOV adjust), `OnOverlayChanged` → `WBP_HUD` (screen effects), `OnWallProximityChanged` → ABP_Arms + +**Edge Cases:** +- Rapid visibility changes don't cause rendering glitches due to fade transitions +- Overlays stack: intensity values combined via material logic +- Hidden mode overrides all — hides everything regardless of other settings + +--- + +## 14 — BPC_CameraStateLayer: Dynamic Camera Controller + +**What It Does:** Centralizes all camera-modifying logic: field-of-view changes per state, camera shake system with priority queuing, post-process overrides (vignette, chromatic aberration, color grading), head bob, and location/rotation offsets per game state. Other systems request camera changes here instead of directly manipulating the camera. + +**How It Works Internally:** + +**Camera State System:** +- 10 states: Default, Aiming, Sprinting, Crouching, Peeking, Hiding, Stressed, Injured, Death, Cutscene +- Each has `S_CameraStateConfig`: TargetFOV, BlendSpeed, HeadOffset, HeadRotation, pitch constraints, head bob settings +- `RequestCameraState(NewState, bImmediate)` sets targets and starts blending +- `BlendToTargetFOV()` runs per tick: smooth interpolation to target FOV +- `BlendCameraOffset()` interpolates head position/rotation offsets + +**Camera Shake System:** +- `S_CameraShakeRequest`: ShakeClass, Scale, Priority (Low/Medium/High/Cinematic), Duration, bIsLooping, Tag +- Priority system: higher priority shakes override lower; same tag replaces existing +- `PlayCameraShake(ShakeRequest)` adds to ActiveShakeRequests map and starts playback +- If Duration > 0: timer auto-stops shake +- `StopCameraShake(ShakeTag)` removes specific shake + +**Post-Process Overrides:** +- `S_PostProcessOverride`: vignette, chromatic aberration, color grading LUT +- `ApplyPostProcessOverride(Override, bInstant)` blends Material Parameter Collection values +- `ClearPostProcessOverride(BlendTime)` returns to defaults +- Used by stress system (CA at high stress), injury (vignette), death (fade to black) + +**Dynamic Effects:** +- **Sprint:** FOV widens by SprintFOVMultiplier +- **Stress:** `UpdateStressPulse()` applies sine-wave FOV oscillation for "unease" feel +- **Crouch:** slight downward offset +- **Peek:** camera moves to hide spot peek socket +- **Hiding:** constricted FOV, darkness effect + +**Reactivity:** +- Binds to `BPC_MovementStateSystem.OnMovementModeChanged` → Sprint/Crouch/Default FOV +- Binds to `BPC_StressSystem.OnStressTierChanged` → stress effects at Panicked+ +- Binds to `BPC_HealthSystem.OnDamageTaken` → injury shake, vignette +- Called directly by `BPC_HidingSystem` for peek/hide camera states + +**Implementation Patterns:** +- Uses Material Parameter Collection (MPC_CameraEffects) for post-process parameters +- Camera shakes authored as Blueprint Camera Shake classes +- Pitch constraints enforced during hiding/peeking states +- All blending is frame-rate independent via DeltaTime + +**Integration Points:** +- **Broadcasts:** `OnCameraStateChanged`, `OnFOVChanged`, `OnShakeStarted/Ended`, `OnPostProcessChanged` +- **Called by:** Any system needing camera change +- **Key consumers:** PlayerCameraManager (direct application), `WBP_HUD` (post-process materials) + +**Edge Cases:** +- Shake with same tag and lower priority is ignored (not stacked) +- Rapid FOV switching blends smoothly — no snap transitions +- Stress pulse uses sine wave to prevent jitter +- All active shakes stopped on component destroy or pawn unpossess + +--- + +## 15 — BPC_PlayerMetricsTracker: Analytics & Playstyle + +**What It Does:** Records player behavior metrics throughout a session: accuracy (shots fired vs hit), deaths count, damage dealt/received, items used, hiding time, sprint time, objectives completed. Used by `BPC_PlaystyleClassifier` to categorize player as Aggressive/Stealthy/Explorer/Balanced, and by `BPC_DifficultyManager` for adaptive tuning. + +**How It Works Internally:** +- Listens to relevant dispatchers from HealthSystem (deaths), WeaponBase (shots/hits), InventorySystem (items used), HidingSystem (hide time), MovementState (sprint time) +- Accumulates counters and ratios internally +- Provides query functions for other adaptive systems +- Persists across sessions via `I_Persistable` for long-term playstyle tracking + +**Implementation Patterns:** +- Pure listener — never calls into other systems, only receives events +- All metrics are gameplay-tag keyed for extensibility +- Ratios computed on demand, not continuously + +--- + +## Common Implementation Patterns in This Category + +1. **Cascade Architecture:** Systems don't poll each other — they bind to dispatchers. Health change → stress reaction → camera effect → movement penalty. Each system reacts independently. +2. **Normalized Values for UI:** `GetHealthNormalised()`, `GetStaminaNormalised()`, `GetStressNormalised()` all return 0.0-1.0 — UI widgets interpolate smoothly. +3. **Timer-Based Ticks:** Regen, drain, stress decay all use 0.1s interval timers — not event tick. Better performance. +4. **State Not Polling:** Camera doesn't ask "am I sprinting?" — it binds to `OnMovementModeChanged` and reacts. +5. **Threshold Not Continuous:** Stress/hiding systems use tier/state thresholds — other systems react to tier changes, not raw values. +6. **Data Config Everything:** Damage resistances, movement speeds, stamina drain rates, stress thresholds — all in exposed variables or Data Assets, never hardcoded. + +--- + +*Developer Reference v1.0 — 02 Player Systems. Companion to docs/blueprints/02-player/ specs.*