# 15 — Player Metrics Tracker (`BPC_PlayerMetricsTracker`)

## Purpose
Records and exposes player behaviour telemetry throughout a play session. Metrics feed into the adaptive difficulty system, achievement tracking, end-game stats screen, and narrative pacing adjustments. This is a pure data-gathering component — it never blocks or modifies gameplay directly.

## Dependencies
- **Requires:** `GI_GameFramework` (session start/end events), `DA_GameDifficulty` (adaptive thresholds)
- **Required By:** `BPC_AdaptiveDifficulty` (consumes metrics), `WBP_StatsScreen` (end-game display), `GM_CoreGameMode` (death/checkpoint metrics)
- **Engine/Plugin Requirements:** `FTimerHandle` for play time tracking

## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `ActorComponent` |
| **Class Type** | Blueprint Component |
| **Asset Path** | `Content/Framework/Player/BPC_PlayerMetricsTracker` |
| **Implements Interfaces** | `I_Persistable` (save/load metrics) |

---

## 1. Enums

### `E_MetricEventType`
| Value | Description |
|-------|-------------|
| `Death = 0` | Player died |
| `HideStarted = 1` | Player entered a hide spot |
| `HideEnded = 2` | Player exited a hide spot |
| `ItemCollected = 3` | Player picked up an item |
| `EnemyEncountered = 4` | Player entered an enemy detection range |
| `EnemyEvaded = 5` | Player escaped enemy detection |
| `StressPeakReached = 6` | Stress tier exceeded Distressed |
| `AreaExplored = 7` | Player entered a new area/chapter |
| `NarrativeEvent = 8` | Story beat triggered |
| `CombatEvent = 9` | Player used a weapon or was attacked |

---

## 2. Structs

### `S_BehaviourEvent`
| Field | Type | Description |
|-------|------|-------------|
| `Timestamp` | `Float` | Game time in seconds when event occurred |
| `EventType` | `E_MetricEventType` | What happened |
| `ContextTag` | `GameplayTag` | Tagged context for filtering |
| `FloatValue` | `Float` | Numeric payload (e.g. distance, intensity) |
| `StringValue` | `String` | Text payload (e.g. location name, enemy ID) |
| `Location` | `Vector` | World location where event occurred |

### `S_MetricsSnapshot`
| Field | Type | Description |
|-------|------|-------------|
| `TotalPlayTime` | `Float` | Total seconds played |
| `TimesDied` | `Integer` | Total deaths this session |
| `TimesHid` | `Integer` | Total hide events |
| `TotalDistanceWalked` | `Float` | Cumulative walking distance (cm) |
| `TotalDistanceSprinted` | `Float` | Cumulative sprinting distance (cm) |
| `ItemsCollected` | `Integer` | Total items picked up |
| `UniqueItemsCollected` | `Set<FName>` | Names of unique items collected |
| `EnemiesEncountered` | `Integer` | Total unique enemy encounters |
| `EnemiesEvaded` | `Integer` | Total successful evasions |
| `PeakStressTier` | `Float` | Highest stress tier reached (as float) |
| `StressPeakCount` | `Integer` | Times stress exceeded Distressed threshold |
| `NarrativeBeatsCompleted` | `Integer` | Story triggers activated |
| `AreasExplored` | `Set<FName>` | Unique areas/chapters visited |
| `CombatEvents` | `Integer` | Total combat interactions |

### `S_TimeSegment`
| Field | Type | Description |
|-------|------|-------------|
| `SegmentLabel` | `String` | Label for this time segment |
| `StartTime` | `Float` | Game time at segment start |
| `EndTime` | `Float` | Game time at segment end |
| `StressAverage` | `Float` | Average stress during segment |
| `MetricsDelta` | `S_MetricsSnapshot` | Metrics accumulated in this segment |

---

## 3. Variables

### Configuration (Instance Editable, Expose On Spawn)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `bTrackDetailedEvents` | `Boolean` | `true` | `Metrics Config` | If true, store full `S_BehaviourEvent` history |
| `MaxEventHistory` | `Integer` | `5000` | `Metrics Config` | Cap on stored events to prevent memory bloat |
| `bAutoSaveMetrics` | `Boolean` | `true` | `Metrics Config` | Auto-save metrics via I_Persistable on quit |
| `SnapshotInterval` | `Float` | `60.0` | `Metrics Config` | Seconds between automatic snapshot writes |

### Computed / Runtime (Public, Blueprint Read Only)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentSnapshot` | `S_MetricsSnapshot` | `-` | `Runtime Metrics` | Live accumulated metrics this session |
| `bIsTrackingSession` | `Boolean` | `false` | `Runtime Metrics` | Whether currently in a tracked session |
| `TimeSegmentHistory` | `Array<S_TimeSegment>` | `[]` | `Runtime Metrics` | Historical segments for analysis |
| `CurrentSegment` | `S_TimeSegment` | `-` | `Runtime Metrics` | Active time segment |

### Internal (Private / Protected, No Expose)

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `SessionStartTime` | `Float` | `0.0` | `Timing` | Game time when session started |
| `CachedEventHistory` | `Array<S_BehaviourEvent>` | `[]` | `History` | Full event history (if tracking enabled) |
| `LastPosition` | `Vector` | `(0,0,0)` | `Tracking` | Previous frame position for distance calc |
| `SnapshotTimerHandle` | `FTimerHandle` | `-` | `Timing` | Timer for automatic snapshots |

### Replicated (if multiplayer)

| Variable | Type | Condition | Description |
|----------|------|-----------|-------------|
| `CurrentSnapshot` | `S_MetricsSnapshot` | `Replicated` | Synced session metrics |

---

## 4. Functions

### Public Functions

#### `StartSession` → `void`
- **Description:** Begins a new metrics tracking session. Resets snapshot.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `SegmentLabel` | `String` | Label for initial segment (e.g. "Chapter 1") |
- **Blueprint Authority:** Any
- **Flow:**
  1. Set SessionStartTime = Current Game Time
  2. Reset CurrentSnapshot to zero values
  3. Initialize CurrentSegment with label and start time
  4. Set bIsTrackingSession = true
  5. Start snapshot timer
  6. Fire OnSessionStarted

#### `EndSession` → `S_MetricsSnapshot`
- **Description:** Ends the tracking session and returns final snapshot.
- **Parameters:** None
- **Flow:**
  1. Finalize CurrentSegment end time
  2. Finalize CurrentSnapshot.PlayTime
  3. Add CurrentSegment to TimeSegmentHistory
  4. Set bIsTrackingSession = false
  5. Clear snapshot timer
  6. Fire OnSessionEnded
  7. Return CurrentSnapshot

#### `RecordEvent` → `void`
- **Description:** Records a single behaviour event into history and updates snapshot.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `EventType` | `E_MetricEventType` | Type of event |
  | `ContextTag` | `GameplayTag` | Filtering tag |
  | `FloatValue` | `Float` | Numeric payload |
  | `StringValue` | `String` | Text payload |
- **Flow:**
  1. If not bIsTrackingSession: return
  2. Create S_BehaviourEvent with current timestamp
  3. If bTrackDetailedEvents and event count < MaxEventHistory:
     - Add event to CachedEventHistory
  4. Update CurrentSnapshot based on EventType:
     - Death: TimesDied++
     - HideStarted: TimesHid++
     - ItemCollected: ItemsCollected++, add name to UniqueItemsCollected
     - EnemyEncountered: EnemiesEncountered++
     - EnemyEvaded: EnemiesEvaded++
     - StressPeakReached: StressPeakCount++, update PeakStressTier if higher
     - NarrativeEvent: NarrativeBeatsCompleted++
     - AreaExplored: add to AreasExplored
     - CombatEvent: CombatEvents++
  5. Fire OnEventRecorded

#### `RecordDistanceTraveled` → `void`
- **Description:** Called by movement system or tick to log distance.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `DistanceDelta` | `Float` | Distance moved this frame |
  | `bWasSprinting` | `Boolean` | Whether player was sprinting |
- **Flow:**
  1. If bWasSprinting: CurrentSnapshot.TotalDistanceSprinted += Delta
  2. Else: CurrentSnapshot.TotalDistanceWalked += Delta

#### `GetMetricsSnapshot` → `S_MetricsSnapshot`
- **Description:** Returns a copy of the current live snapshot.
- **Parameters:** None
- **Flow:** Return CurrentSnapshot

#### `GetEventHistory` → `Array<S_BehaviourEvent>`
- **Description:** Returns recorded event history.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `bFiltered` | `Boolean` | If true, apply filter |
  | `FilterType` | `E_MetricEventType` | Only return events of this type |
- **Flow:**
  1. If bFiltered: return filtered CachedEventHistory by FilterType
  2. Else: return CachedEventHistory

#### `GetPlayTimeFormatted` → `String`
- **Description:** Returns total play time as formatted string (HH:MM:SS).
- **Parameters:** None
- **Flow:** Format SecondsToTimeString(SessionDuration)

### Protected / Private Functions

#### `OnTickDistance (Tick)` → `void`
- **Description:** Each tick, compute distance from LastPosition. Only active when tracking.
- **Flow:**
  1. If not bIsTrackingSession: return
  2. Get current actor location
  3. Delta = Distance(LastPosition, CurrentLocation)
  4. If Delta < 10.0: skip (noise threshold)
  5. Check movement mode from BPC_MovementStateSystem
  6. Call RecordDistanceTraveled(Delta, bWasSprinting)
  7. Update LastPosition = CurrentLocation

#### `CreateSnapshot` → `void`
- **Description:** Timer callback to create periodic snapshots.
- **Flow:**
  1. Copy CurrentSnapshot as snapshot
  2. Finalize CurrentSegment end time
  3. Store snapshot delta in CurrentSegment.MetricsDelta
  4. Add CurrentSegment to TimeSegmentHistory
  5. Start new CurrentSegment

---

## 5. Event Dispatchers

| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnSessionStarted` | `String SegmentLabel` | `Public` | Fired when a tracking session begins |
| `OnSessionEnded` | `S_MetricsSnapshot FinalSnapshot` | `Public` | Fired when a tracking session ends |
| `OnEventRecorded` | `S_BehaviourEvent Event` | `Public` | Fired each time an event is recorded |
| `OnMetricsThresholdReached` | `E_MetricEventType EventType`, `int CurrentValue`, `int Threshold` | `Public` | Fired when a metric crosses a threshold |
| `OnSnapshotCreated` | `S_TimeSegment Segment` | `Public` | Fired when a periodic snapshot is written |

---

## 6. Overridden Events / Custom Events

### Event: `BeginPlay`
- **Description:** Cache owner reference, start session if game has started.
- **Flow:**
  1. Get owning pawn
  2. Bind to GI_GameFramework.OnGamePhaseChanged
  3. If game phase is Playing: StartSession("Initial")

### Custom Event: `OnGamePhaseChangedHandler`
- **Description:** Auto-start/stop session based on game phase.
- **Flow:**
  1. If NewPhase == Playing: StartSession("Resume")
  2. If NewPhase == Menu / Transition / Ended: EndSession()

---

## 7. Blueprint Graph Logic Flow

```mermaid
flowchart TD
    A[RecordEvent called] --> B{Is session active?}
    B -->|No| C[Return]
    B -->|Yes| D[Build S_BehaviourEvent]
    D --> E{Has history space?}
    E -->|Yes| F[Append to CachedEventHistory]
    E -->|No| G[Skip history store]
    G --> H[Update CurrentSnapshot counters]
    F --> H
    H --> I{Threshold crossed?}
    I -->|Yes| J[Fire OnMetricsThresholdReached]
    I -->|No| K[Fire OnEventRecorded]
    J --> K
    
    L[Tick: RecordDistanceTraveled] --> M[Get current location]
    M --> N[Distance from LastPosition]
    N --> O{Delta > 10 cm?}
    O -->|No| P[Skip]
    O -->|Yes| Q[Update snapshot distance]
    Q --> R[Set LastPosition]
    
    S[SnapshotTimer fires] --> T[Copy CurrentSnapshot]
    T --> U[Write segment to history]
    U --> V[Fire OnSnapshotCreated]
```

---

## 8. Communication Matrix

| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| `BPC_PlayerMetricsTracker` | `Direct call (other)` | `GetMetricsSnapshot` -> `BPC_AdaptiveDifficulty` |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnEventRecorded` -> `BPC_AdaptiveDifficulty` (real-time feed) |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnSessionEnded` -> `WBP_StatsScreen` (end-game display) |
| `BPC_PlayerMetricsTracker` | `Dispatcher` | `OnMetricsThresholdReached` -> `GM_CoreGameMode` (achievements) |
| `BPC_HealthSystem` | `Direct` | Calls `RecordEvent(Death)` on player death |
| `BPC_HidingSystem` | `Direct` | Calls `RecordEvent(HideStarted/HideEnded)` |
| `BPC_StressSystem` | `Direct` | Calls `RecordEvent(StressPeakReached)` |
| `GI_GameFramework` | `Dispatcher` | `OnGamePhaseChanged` -> `BPC_PlayerMetricsTracker` (start/stop) |
| `BPC_NarrativeManager` | `Direct` | Calls `RecordEvent(NarrativeEvent)` on story beat |
| `BPC_WeaponSystem` | `Direct` | Calls `RecordEvent(CombatEvent)` on attack |
| `BPC_InventoryManager` | `Direct` | Calls `RecordEvent(ItemCollected)` on pickup |

---

## 9. Validation / Testing Checklist

- [ ] StartSession resets all counters to zero
- [ ] Distance tracking correctly accumulates walking vs sprinting
- [ ] Events are capped at MaxEventHistory and do not overflow
- [ ] Snapshot timer creates periodic snapshots at correct interval
- [ ] EndSession returns accurate final snapshot
- [ ] Filtered event history returns only matching event types
- [ ] Threshold dispatchers fire at correct metric values
- [ ] I_Persistable save/load preserves snapshot across sessions
- [ ] Edge case: Rapid events within one frame all recorded
- [ ] Edge case: Session ends with zero events — snapshot is valid zero-state
- [ ] Edge case: Distance tracking ignores teleportation (delta > 5000 cm)
- [ ] Formatted play time displays correctly for all durations

---

## 10. Reuse Notes

- The metrics snapshot is designed to be consumed by `BPC_AdaptiveDifficulty` for dynamic difficulty adjustment.
- Event history can be serialised to disk via `I_Persistable` for cross-session analytics.
- The same `BPC_PlayerMetricsTracker` can be attached to any character blueprint — not just the player.
- Threshold values are not defined here; they live in `DA_GameDifficulty` or `DA_AdaptiveSettings`.
- For debugging, call `GetEventHistory(false)` to dump all events to log.

---

## 11. Multiplayer Networking

### Replication
| Variable | Condition | Purpose |
|----------|-----------|---------|
| `CurrentSnapshot` | `Replicated` | Synced for scoreboard, playstyle classification |

### Authority
- **Server** accumulates all metrics (listens to gameplay dispatchers server-side).
- **Server** creates periodic snapshots and replicates.
- **Clients** only read metrics (for local HUD/scoreboard display).

### Server RPCs
| RPC | Direction | Description |
|-----|-----------|-------------|
| `Server_LogEvent` | Client→Server | Client reports a metric event. Server validates before recording. |

### Anti-Cheat
- Server validates `Server_LogEvent` calls: damage dealt must match server calculations, distance traveled must be plausible, item pickups validated by inventory system.

---

*Blueprint Spec: Player Metrics Tracker. Conforms to TEMPLATE.md v1.0 — part of the UE5 Modular Game Framework.*