# BPC_PlaystyleClassifier — Playstyle Classifier

## Blueprint Spec — UE 5.5–5.7

---

### Parent Class
`ActorComponent`

### Dependencies
- **Requires:** [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Reads behaviour metrics (AggressionScore, CautionScore, ExplorationScore)
- **Required By:** [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) — Subscribes to `OnPlaystyleChanged`
- **Required By:** [`BPC_BehaviourVariantSelector`](../09-ai/BPC_BehaviourVariantSelector.md) — AI adapts to playstyle
- **Required By:** [`BPC_PacingDirector`](BPC_PacingDirector.md) — Pacing adjusted by playstyle
- **Engine/Plugin Requirements:** GameplayTags (for playstyle tag output), Timer system

### Purpose
Analyses player behaviour metrics from BPC_PlayerMetricsTracker and classifies their playstyle in real time. Outputs a dominant playstyle tag (Aggressive, Cautious, Explorer, etc.) that feeds into the adaptive environment director and AI behaviour systems.

---

## 1. Enums

```text
Enum Name: E_PlaystyleTag
(DisplayName = "Playstyle Tag")

Values:
  Aggressive      = 0    // Player favours combat and direct confrontation
  Cautious        = 1    // Player favours hiding, sneaking, and avoidance
  Explorer        = 2    // Player favours off-path exploration and discovery
  Passive         = 3    // Player avoids interaction, moves slowly
  Speedrunner     = 4    // Player rushes through content, skips optional
  Completionist   = 5    // Player exhaustively searches every area
```

*Values are used as Gameplay Tags for routing to adaptive systems.*

---

## 2. Structs

*No new structs defined. Reads from [`S_BehaviourEvent`](../02-player/15_BPC_PlayerMetricsTracker.md).*

---

## 3. Variables

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

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `ClassificationInterval` | Float | 15.0 | Classifier | Seconds between playstyle re-evaluations |
| `AggressionWeight` | Float | 1.0 | Classifier | Multiplier for aggression score influence |
| `CautionWeight` | Float | 1.0 | Classifier | Multiplier for caution score influence |
| `ExplorationWeight` | Float | 1.0 | Classifier | Multiplier for exploration score influence |
| `ClassificationThreshold` | Float | 10.0 | Classifier | Minimum score delta required to change classification |

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

| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentPlaystyleTag` | GameplayTag | Explorer | Internal | Dominant playstyle classification |
| `PreviousPlaystyleTag` | GameplayTag | None | Internal | Last classification for change detection |
| `ClassificationTimer` | TimerHandle | — | Internal | Timer for periodic re-evaluation |

---

## 4. Functions

### Public Functions

#### `ClassifyPlaystyle` → `GameplayTag`
- **Description:** Reads metrics from BPC_PlayerMetricsTracker, calculates weighted scores, and determines the dominant playstyle tag.
- **Parameters:** None
- **Blueprint Authority:** Any
- **Flow:** Get metrics → multiply by weights → find highest scoring playstyle → return tag

#### `GetCurrentPlaystyle` → `GameplayTag`
- **Description:** Returns the current dominant playstyle tag.
- **Parameters:** None
- **Blueprint Authority:** Any

#### `GetPlaystyleScores` → `Map (GameplayTag → Float)`
- **Description:** Returns the weighted score for each playstyle tag for external analysis.
- **Parameters:** None
- **Blueprint Authority:** Any

#### `ForcePlaystyle` → `void`
- **Description:** Overrides the playstyle classification for debugging or scripted sequences.
- **Parameters:**
  | Param | Type | Description |
  |-------|------|-------------|
  | `PlaystyleTag` | GameplayTag | Forced classification |
- **Blueprint Authority:** Any

---

## 5. Event Dispatchers

| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnPlaystyleChanged` | OldTag: GameplayTag, NewTag: GameplayTag | Public | Playstyle classification changed |
| `OnPlaystyleReclassified` | NewTag: GameplayTag | Public | Periodic re-evaluation completed (fires even if unchanged) |

---

## 6. Overridden Events / Custom Events

### Event: `BeginPlay`
- **Description:** Starts the classification timer. Subscribes to metrics changes if needed.
- **Flow:**
  1. Start ClassificationTimer with ClassificationInterval repeating
  2. Perform initial classification immediately

### Event: `TickClassification`
- **Description:** Timer callback. Runs ClassifyPlaystyle and broadcasts if changed.
- **Flow:**
  1. Read BPC_PlayerMetricsTracker.AggressionScore, CautionScore, ExplorationScore
  2. Calculate weighted scores
  3. Determine dominant playstyle tag (highest weighted score)
  4. If new tag differs from CurrentPlaystyleTag AND score delta exceeds ClassificationThreshold:
     - Set PreviousPlaystyleTag = CurrentPlaystyleTag
     - Set CurrentPlaystyleTag = new tag
     - Broadcast OnPlaystyleChanged
  5. Always broadcast OnPlaystyleReclassified

---

## 7. Blueprint Graph Logic Flow

```mermaid
flowchart TD
    A[BeginPlay] --> B[Start ClassificationTimer]
    B --> C[Timer fires each ClassificationInterval seconds]
    C --> D[Read AggressionScore from BPC_PlayerMetricsTracker]
    D --> E[Read CautionScore from BPC_PlayerMetricsTracker]
    E --> F[Read ExplorationScore from BPC_PlayerMetricsTracker]
    F --> G[Calculate weighted scores:]
    G --> H[AggressionScore * AggressionWeight]
    H --> I[CautionScore * CautionWeight]
    I --> J[ExplorationScore * ExplorationWeight]
    J --> K[Also factor in: DeathCount, HideCount, ItemsCollected, DistanceTravelled]
    K --> L[Map derived values to playstyle tags]
    L --> M[Find highest weighted playstyle tag]
    M --> N{New tag != CurrentPlaystyleTag?}
    N -->|Yes| O{Score delta >= ClassificationThreshold?}
    O -->|Yes| P[Broadcast OnPlaystyleChanged]
    O -->|No| Q[Keep current classification]
    N -->|No| Q
    P --> R[Update CurrentPlaystyleTag]
    R --> S[Broadcast OnPlaystyleReclassified]
    Q --> S
```

---

## 8. Communication Matrix

| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Direct read | `AggressionScore`, `CautionScore`, `ExplorationScore`, `DeathCount`, `HideCount` |
| [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) | Dispatcher (`OnPlaystyleChanged`) | `OldTag: GameplayTag`, `NewTag: GameplayTag` |
| [`BPC_BehaviourVariantSelector`](../09-ai/BPC_BehaviourVariantSelector.md) | Dispatcher (`OnPlaystyleChanged`) | `NewTag: GameplayTag` |
| [`BPC_PacingDirector`](BPC_PacingDirector.md) | Direct read | `CurrentPlaystyleTag` |
| [`SS_AchievementSystem`](../11-meta/SS_AchievementSystem.md) | Dispatcher (`OnPlaystyleChanged`) | Playstyle tag for adaptive achievement checks |

---

## 9. Validation / Testing Checklist

- [ ] E_PlaystyleTag enum has all 6 values defined
- [ ] ClassificationInterval timer fires at correct interval
- [ ] Weighted scores correctly calculated from metrics
- [ ] OnPlaystyleChanged fires when classification changes significantly
- [ ] ClassificationThreshold prevents minor fluctuations from causing changes
- [ ] ForcePlaystyle overrides the automatic classification
- [ ] Edge case: BPC_PlayerMetricsTracker not present → default to Explorer
- [ ] Edge case: all scores zero → classify as Passive
- [ ] Edge case: timer fires during cutscene → classification still runs, adapt on exit

---

## 10. Reuse Notes

- AggressionWeight, CautionWeight, ExplorationWeight can be tuned per project to prioritise different behaviours
- ClassificationThreshold prevents "flickering" between similar playstyles on minor metric changes
- For non-adaptive games, set ClassificationInterval to 0 to disable or force a fixed playstyle
- Extend with new E_PlaystyleTag values per project (e.g., "Pacifist", "Collector")
- Classification is player-pawn-relative — in co-op, each player has their own classifier

---

*Specification based on Master Section 9.1, line 2699.*