add blueprints

This commit is contained in:
Lefteris Notas
2026-05-19 13:22:27 +03:00
parent f71bc678b2
commit 411edea8ce
138 changed files with 23330 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
# BPC_RareEventSystem — Rare Event System
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- **Requires:** [`DA_RareEvent`](../14-data-assets/DA_RareEvent.md) — Rare event definitions with probability and conditions
- **Requires:** [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) — Reads narrative flags for eligibility conditions
- **Required By:** [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) — Requests rare event roll
- **Engine/Plugin Requirements:** GameplayTags, Timer system, Random stream
### Purpose
Controls ultra-rare, randomised world events that can occur once per session or with very low probability: hidden messages, brief hallucinations, entity sightings, environmental easter eggs. Tracks which rare events have already fired to prevent repeats and evaluates complex eligibility conditions.
---
## 1. Enums
*No new enums defined. Uses GameplayTags for event and condition identification.*
---
## 2. Structs
### `S_RareEventCondition`
| Field | Type | Description |
|-------|------|-------------|
| `RequiredFlags` | Array of GameplayTag | All must be true for eligibility |
| `ForbiddenFlags` | Array of GameplayTag | None must be true for eligibility |
| `RequiredPlaystyleTag` | GameplayTag | Player must currently have this playstyle |
| `MinSessionTime` | Float | Minimum session elapsed seconds |
| `MinDeathCount` | Integer | Minimum player deaths this session |
| `RequiredChapterTag` | GameplayTag | Current chapter must match |
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `RareEventPool` | Array of DA_RareEvent | Empty | RareEventConfig | All available rare events for this session |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `FiredRareEvents` | Set of GameplayTag | Empty | Internal | Already triggered rare events (prevent repeat) |
| `EligibilityConditions` | Map (GameplayTag → S_RareEventCondition) | Empty | Internal | Resolved eligibility conditions per event |
| `RollTimer` | TimerHandle | — | Internal | Periodic roll timer |
| `RollInterval` | Float | 60.0 | Internal | Seconds between rare event roll checks |
---
## 4. Functions
### Public Functions
#### `RollForRareEvent` → `DA_RareEvent`
- **Description:** Evaluates all eligible rare events, rolls probability, and returns a selected event (or None). Fires the event if one is selected.
- **Parameters:** None
- **Blueprint Authority:** Any
- **Flow:** Filter pool by eligibility → exclude fired events → for each eligible, roll probability → if roll passes, select and fire → add to FiredRareEvents → broadcast OnRareEventFired → return selected event
#### `IsEventEligible` → `Bool`
- **Description:** Checks if a specific rare event's conditions are currently met.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `EventTag` | GameplayTag | Rare event identifier |
- **Blueprint Authority:** Any
- **Flow:** Read S_RareEventCondition from map → check RequiredFlags against narrative state → check ForbiddenFlags → check playstyle match → check session time → check death count → check chapter → return true if all pass
#### `ForceRareEvent` → `void`
- **Description:** Fires a specific rare event immediately, bypassing probability and eligibility checks.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `EventTag` | GameplayTag | Rare event to fire |
- **Blueprint Authority:** Any
#### `GetFiredEvents` → `Set of GameplayTag`
- **Description:** Returns all rare events that have already fired this session.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `GetEligibleEvents` → `Array of DA_RareEvent`
- **Description:** Returns all rare events that are currently eligible (conditions met, not yet fired).
- **Parameters:** None
- **Blueprint Authority:** Any
#### `ResetSession` → `void`
- **Description:** Clears fired events and resets eligibility. Called on new game.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `SetRollInterval` → `void`
- **Description:** Adjusts the periodic roll check interval.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Interval` | Float | New interval in seconds |
- **Blueprint Authority:** Any
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnRareEventFired` | EventTag: GameplayTag | Public | A rare event was triggered |
| `OnRareEventFailed` | EventTag: GameplayTag | Public | Probability roll failed for an eligible event |
| `OnAllRareEventsFired` | — | Public | All events in pool have been fired |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay`
- **Description:** Initialises the rare event pool and starts the periodic roll timer.
- **Flow:**
1. Build EligibilityConditions map from RareEventPool entries
2. Start RollTimer with RollInterval
3. Perform initial roll check
### Event: `TickRoll`
- **Description:** Timer callback. Performs a rare event roll check.
- **Flow:**
1. Call RollForRareEvent
2. If event fired → broadcast OnRareEventFired
3. If all events in pool are fired → stop timer and broadcast OnAllRareEventsFired
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[BeginPlay] --> B[Build EligibilityConditions map]
B --> C[Start RollTimer at RollInterval]
C --> D[Timer fires]
D --> E[GetEligibleEvents]
E --> F{Any eligible events?}
F -->|No| G[Wait for next tick]
F -->|Yes| H[For each eligible event:]
H --> I[Roll random against DA_RareEvent.Probability]
I --> J{Roll <= Probability?}
J -->|Yes| K[Fire the rare event]
J -->|No| L[Broadcast OnRareEventFailed]
K --> M[Add to FiredRareEvents]
M --> N[Broadcast OnRareEventFired]
N --> O{All events fired?}
O -->|Yes| P[Stop RollTimer]
O -->|No| G
L --> H
P --> Q[Broadcast OnAllRareEventsFired]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) | Direct call | `RollForRareEvent()` |
| [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) | Direct read | Narrative flags for eligibility condition checks |
| [`BPC_PlaystyleClassifier`](BPC_PlaystyleClassifier.md) | Direct read | `CurrentPlaystyleTag` for playstyle-gated events |
| [`GS_CoreGameState`](../01-core/06_GS_CoreGameState.md) | Direct read | `ElapsedPlayTime`, `ActiveChapterTag` |
| [`DA_RareEvent`](../14-data-assets/DA_RareEvent.md) | Data asset read | Event def: tag, probability, conditions, actions |
---
## 9. Validation / Testing Checklist
- [ ] S_RareEventCondition struct has all 6 fields
- [ ] RareEventPool is populated with DA_RareEvent assets
- [ ] IsEventEligible correctly checks all condition types
- [ ] RollForRareEvent respects probability values (0.0 = never, 1.0 = always)
- [ ] FiredRareEvents prevents repeat triggering
- [ ] ForceRareEvent bypasses all checks for scripted/debug use
- [ ] RollTimer stops when all events exhausted
- [ ] Edge case: RareEventPool empty → no errors, timer still runs harmlessly
- [ ] Edge case: all events fired → OnAllRareEventsFired broadcasts once
- [ ] Edge case: rapid probability rolls don't fire same event twice (FiredRareEvents set check)
---
## 10. Reuse Notes
- RareEventPool is population-defined — populate per project with unique events
- Probability values in DA_RareEvent are designer-tuned per event (0.001 for ultra-rare, 0.1 for uncommon)
- Eligibility conditions support complex gating (chapter, playstyle, time, death count)
- For non-horror games, fill pool with easter eggs and hidden secrets instead of scary events
- Extend S_RareEventCondition with project-specific gates (e.g., specific item held, specific ending achieved)
---
*Specification based on Master Section 9.9, line 2922.*

View File

@@ -0,0 +1,213 @@
# BPC_ScareEventSystem — Scare Event System
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- **Requires:** [`BPC_StressSystem`](../02-player/10_BPC_StressSystem.md) — Reads current stress tier to set ActiveScareTier
- **Requires:** [`DA_ScareEvent`](../14-data-assets/DA_ScareEvent.md) — Scare event definitions
- **Required By:** [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) — Trigger stingers (via `PlayMusicStinger()`)
- **Required By:** [`BPC_LightEventController`](BPC_LightEventController.md) — Trigger scare lighting
- **Required By:** `I_ScareTarget` actors — Broadcast scare events to world actors
- **Required By:** [`WBP_ScreenEffectController`](../06-ui/WBP_ScreenEffectController.md) — Jump scare flash
- **Engine/Plugin Requirements:** GameplayTags, Audio, Timer system
### Purpose
Manages the scheduling and firing of scare events: jump scares, ambient scares, audio stingers, and environmental anomalies. Enforces scare cooldowns to prevent scare saturation, selects appropriate scare tier from player stress level, and tracks scare history to avoid repetition.
---
## 1. Enums
```text
Enum Name: E_ScareTier
(DisplayName = "Scare Tier")
Values:
Ambient = 0 // Background unease: creaks, whispers, distant sounds
Minor = 1 // Small startle: object falls, door creaks nearby
Moderate = 2 // Noticeable scare: shadow figure, loud noise
Major = 3 // Intense scare: entity appears, lights fail
JumpScare = 4 // Full jump scare: flash, stinger, entity lunge
Cinematic = 5 // Scripted cinematic scare sequence
```
---
## 2. Structs
*No new structs defined. Uses [`DA_ScareEvent`](../14-data-assets/DA_ScareEvent.md) for event definitions.*
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `ScareEventPool` | Array of DA_ScareEvent | Empty | ScareConfig | All available scare events for this level |
| `ScareCooldownMin` | Float | 30.0 | ScareConfig | Minimum seconds between scare events |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `LastScareTime` | Float | 0.0 | Internal | Game time of last scare event |
| `ActiveScareTier` | E_ScareTier | Ambient | Internal | Current scare tier matched to stress tier |
| `ScareHistory` | Array of GameplayTag | Empty | Internal | Recently fired scare event tags (avoid repeat) |
| `ScareHistoryMaxSize` | Integer | 10 | Internal | Max history entries before oldest are removed |
---
## 4. Functions
### Public Functions
#### `RequestScareEvent` → `Bool`
- **Description:** Checks conditions and fires a scare event if cooldown passed and valid events available. Returns true if a scare was fired.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `TargetTier` | E_ScareTier | Desired scare intensity (None = use ActiveScareTier) |
- **Blueprint Authority:** Any
- **Flow:** Check cooldown → filter pool by tier → exclude history → select event → execute → add to history → update timer
#### `SetActiveScareTier` → `void`
- **Description:** Updates the active scare tier. Called when stress tier changes.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Tier` | E_ScareTier | New active scare tier |
- **Blueprint Authority:** Any
#### `GetActiveScareTier` → `E_ScareTier`
- **Description:** Returns the currently active scare tier.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `ExecuteScareEvent` → `void`
- **Description:** Executes a specific scare event immediately, bypassing cooldown and history checks.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `ScareEvent` | DA_ScareEvent | Specific event to fire |
- **Blueprint Authority:** Any
- **Flow:** Route to audio → route to lighting → route to screen effects → broadcast I_ScareTarget
#### `GetEligibleScares` → `Array of DA_ScareEvent`
- **Description:** Returns all scare events eligible for the current tier, excluding recently fired ones.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Tier` | E_ScareTier | Filter tier |
- **Blueprint Authority:** Any
#### `ResetScareHistory` → `void`
- **Description:** Clears scare history, allowing all events to be eligible again. Called on level load.
- **Parameters:** None
- **Blueprint Authority:** Any
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnScareEventFired` | EventTag: GameplayTag, Tier: E_ScareTier | Public | Scare event executed |
| `OnScareTierChanged` | OldTier: E_ScareTier, NewTier: E_ScareTier | Public | Active scare tier changed |
| `OnScareCooldownExpired` | — | Public | Cooldown period elapsed, scares available again |
| `OnJumpScareTriggered` | EventTag: GameplayTag | Public | Jump scare specifically fired |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay`
- **Description:** Subscribes to [`BPC_StressSystem`](../02-player/10_BPC_StressSystem.md)`OnStressTierChanged` to map stress to scare tier.
- **Flow:**
1. Find BPC_StressSystem and subscribe
2. Map initial stress tier to scare tier
3. Set ActiveScareTier
### Event: `OnStressTierChanged`
- **Description:** Called when player stress tier changes. Remaps stress tier to scare tier.
- **Flow:**
1. Map: Calm→Ambient, Low→Minor, Mid→Moderate, High→Major, Critical→JumpScare
2. SetActiveScareTier(mapped tier)
3. Broadcast OnScareTierChanged
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[BeginPlay] --> B[Subscribe to StressSystem.OnStressTierChanged]
B --> C[Set initial ActiveScareTier from stress]
C --> D[Idle]
D --> E{AdaptiveDirector requests scare?}
E --> F{ScareCooldown elapsed?}
F -->|No| G[Skip — on cooldown]
F -->|Yes| H[GetEligibleScares ActiveScareTier]
H --> I{Any eligible events?}
I -->|No| G
I -->|Yes| J[Select random from eligible]
J --> K[ExecuteScareEvent]
K --> L[Audio → SS_AudioManager.PlayMusicStinger]
K --> M[Visual → BPC_LightEventController + WBP_ScreenEffectController]
K --> N[World → Broadcast I_ScareTarget to tagged actors]
L --> O[Add to ScareHistory]
M --> O
N --> O
O --> P[Update LastScareTime]
P --> Q[Broadcast OnScareEventFired]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| [`BPC_StressSystem`](../02-player/10_BPC_StressSystem.md) | Dispatcher (`OnStressTierChanged`) | `NewTier: E_StressTier` — maps to E_ScareTier |
| [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) | Direct call | `RequestScareEvent(E_ScareTier)` |
| [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) | Direct call | `PlayMusicStinger(StingerAsset)` |
| [`BPC_LightEventController`](BPC_LightEventController.md) | Direct call | `TriggerLightEvent(EventTag)` |
| [`WBP_ScreenEffectController`](../06-ui/WBP_ScreenEffectController.md) | Direct call | `TriggerJumpScareFlash()` or `TriggerDamageFlash()` |
| `I_ScareTarget` actors | Interface broadcast | `OnScareEventFired(ScareTag)` |
| [`DA_ScareEvent`](../14-data-assets/DA_ScareEvent.md) | Data asset read | Event def: tier, tags, audio, visual, actor targets |
---
## 9. Validation / Testing Checklist
- [ ] E_ScareTier enum has all 6 values
- [ ] RequestScareEvent respects ScareCooldownMin
- [ ] ActiveScareTier correctly mapped from E_StressTier
- [ ] Eligible scares filtered by tier and excluded from history
- [ ] ExecuteScareEvent routes to audio, lighting, screen effects, and I_ScareTarget
- [ ] ScareHistory prevents repeating recent events
- [ ] OnJumpScareTriggered fires for E_ScareTier::JumpScare events
- [ ] Edge case: no eligible scares → RequestScareEvent returns false
- [ ] Edge case: all scares in pool exhausted → reset history or allow repeats
- [ ] Edge case: cooldown active during cinematic → scripted scares bypass cooldown via ExecuteScareEvent
---
## 10. Reuse Notes
- ScareEventPool is populated per level — different levels have different scare content
- ScareCooldownMin prevents scare saturation; tune per project for desired horror intensity
- Stress-to-scare mapping is configurable: calm games can cap at Minor tier
- For non-horror games, leave ScareEventPool empty to disable the system
- I_ScareTarget interface lets any actor react to scares without coupling to this system
---
*Specification based on Master Section 9.5, line 2812.*

View File

@@ -0,0 +1,638 @@
# 132 — MetaSounds Audio Manager (`SS_AudioManager`)
## Purpose
Single entry point for all audio playback in the framework. Every system routes sound through this Game Instance Subsystem instead of calling `UGameplayStatics::PlaySound*` directly. Owns 4 MetaSound buses (SFX, Ambience, Music, Dialogue) → Master Bus, manages room-based reverb zones with crossfade, handles per-category volume from player settings, and accepts gameplay-driven parameter modulation (heart rate, stress, fear, music intensity).
## Dependencies
- **Requires:** `SS_SettingsSystem` (105 — persistent volume settings), `BPC_StateManager` (130 — heart rate + vital signals), `BPC_StressSystem` (10 — stress tier), `BPC_PacingDirector` (98 — music intensity), `BPC_HealthSystem` (08 — player health for muffled audio), `BPC_FearSystem` (90 — fear audio modulation), `DA_AudioSettings` (134 — default config)
- **Required By:** ALL systems that play audio, `BP_RoomAudioZone` (133 — room preset changes), `WBP_SettingsMenu` (57 — volume sliders), `BPC_AudioAtmosphereController` (95 — DEPRECATED, replaced by this)
- **Engine/Plugin Requirements:** MetaSounds plugin (UE 5.5+ default audio engine), GameplayTags plugin
## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `GameInstanceSubsystem` |
| **Class Type** | Game Instance Subsystem |
| **Asset Path** | `Content/Framework/Audio/SS_AudioManager.uasset` |
| **Implements Interfaces** | *(none)* |
---
## 1. Enums
### E_AudioBusCategory — Audio Bus Categories (5 values)
```text
Enum Name: E_AudioBusCategory
(DisplayName = "Audio Bus Category")
Values:
SFX = 0 // Sound effects — spatial, occluded, surface-reactive
Ambience = 1 // Environmental background — room tones, weather
Music = 2 // Dynamic music — layers, intensity bands
Dialogue = 3 // VO and character dialogue — ducking priority
Master = 4 // Master output — overall volume
```
---
## 2. Structs
### `S_AudioBusConfig`
| Field | Type | Description |
|-------|------|-------------|
| `BusCategory` | `E_AudioBusCategory` | Which bus this config applies to |
| `DefaultVolume` | `Float` (01) | Default volume (pre-settings) |
| `MetaSoundSource` | `UMetaSoundSource` | The MetaSound patch for this bus |
| `bSpatialized` | `Boolean` | Use 3D spatialization? |
| `AttenuationSettings` | `USoundAttenuation` | Distance falloff curve |
| `MaxConcurrentSounds` | `Integer` | Voice limiting per bus |
| `DuckingPriority` | `Integer` | 0=lowest, 100=highest (dialogue=100) |
| `DuckAmount` | `Float` (01) | How much to duck other buses when this bus plays |
| `SettingsBindingTag` | `GameplayTag` | Tag for settings lookup (e.g., `Settings.Audio.SFXVolume`) |
### `S_RoomAcousticProfile`
| Field | Type | Description |
|-------|------|-------------|
| `RoomPresetName` | `FName` | Identifier for this room preset |
| `ReverbDensity` | `Float` (01) | How dense the reverb reflections are |
| `ReverbDecayTime` | `Float` (seconds) | RT60 decay time |
| `ReverbWetLevel` | `Float` (01) | Wet/dry mix for reverb |
| `ReflectionsDelay` | `Float` (ms) | Pre-delay before reflections start |
| `RoomSizeScale` | `Float` (0.53.0) | Perceived room size |
| `OcclusionMultiplier` | `Float` (01) | How much walls occlude sound (0=fully open, 1=fully blocked) |
| `LowPassCutoff` | `Float` (Hz) | Low-pass filter cutoff (simulates wall filtering) |
| `bApplyToSFX` | `Boolean` | Apply this room to SFX bus? |
| `bApplyToAmbience` | `Boolean` | Apply this room to ambience bus? |
| `bApplyToDialogue` | `Boolean` | Apply this room to dialogue bus? |
| `TransitionTime` | `Float` (seconds) | Crossfade time when entering this room |
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `BusConfigs` | `Array<S_AudioBusConfig>` | `[SFX, Ambience, Music, Dialogue]` | `Audio Config` | Configuration per bus |
| `RoomPresets` | `Array<DA_RoomAcousticPreset>` | `DefaultPresets` | `Audio Config` | All room acoustic presets |
| `DefaultRoomPreset` | `DA_RoomAcousticPreset` | `Outdoor` | `Audio Config` | Fallback room preset |
| `AudioSettings` | `DA_AudioSettings` | `Defaults` | `Audio Config` | Data asset with volume defaults and bus config |
### Internal (Private / Protected)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `ActiveRoomPreset` | `DA_RoomAcousticPreset` | `None` | `Room` | Current room acoustic profile |
| `PreviousRoomPreset` | `DA_RoomAcousticPreset` | `None` | `Room` | Previous room (for crossfade) |
| `RoomTransitionProgress` | `Float` | `0.0` | `Room` | Crossfade progress 0→1 |
| `RoomTransitionDuration` | `Float` | `0.0` | `Room` | Current transition time remaining |
| `RoomZoneStack` | `Array<DA_RoomAcousticPreset>` | `Empty` | `Room` | Push/pop stack for nested room zones |
| `bIsMuted` | `Boolean` | `false` | `State` | Global mute toggle |
| `bIsPaused` | `Boolean` | `false` | `State` | Global pause state |
| `BusInstances` | `Map<E_AudioBusCategory, UAudioComponent>` | `Empty` | `Bus` | Active bus audio components |
| `ActiveSFXInstances` | `Array<UAudioComponent>` | `Empty` | `SFX` | Pooled SFX playback instances |
| `SFXPoolSize` | `Integer` | `32` | `SFX` | Max concurrent SFX instances |
| `bIsCrossfading` | `Boolean` | `false` | `Room` | Room transition in progress |
| `ActiveDialogueCount` | `Integer` | `0` | `Dialogue` | Number of active dialogue lines |
---
## 4. Functions
### Public Playback Functions
#### `PlaySFX` → `UAudioComponent`
- **Description:** Play spatial SFX through SFX bus at world location. Surface tag selects material impact variant.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Sound` | `UMetaSoundSource` | The MetaSound source to play |
| `Location` | `FVector` | World position for spatialization |
| `SurfaceTag` | `GameplayTag` (optional) | Physical surface tag for material EQ variant |
- **Blueprint Authority:** Any
- **Flow:**
1. Get or create SFX audio component from pool
2. Set sound to `SFXBus` MetaSound source
3. Set world location
4. If SurfaceTag valid: set `SurfaceEQ` parameter on MetaSound
5. Play audio component
6. Return component reference
#### `PlaySFX2D` → `UAudioComponent`
- **Description:** Play non-spatial SFX through SFX bus (UI sounds, heartbeat).
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Sound` | `UMetaSoundSource` | The MetaSound source to play |
- **Blueprint Authority:** Any
- **Flow:** Same as PlaySFX but without world location — 2D playback.
#### `PlayAmbient` → `void`
- **Description:** Crossfade ambient layer on Ambience bus.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Sound` | `UMetaSoundSource` | Ambient layer MetaSound |
| `FadeIn` | `Float` (default 1.0) | Fade-in duration in seconds |
- **Blueprint Authority:** Any
- **Flow:**
1. Get Ambience bus audio component
2. Set new MetaSound source
3. Fade volume from 0 to target over `FadeIn` seconds
4. Route through current room reverb
#### `StopAmbient` → `void`
- **Description:** Fade out current ambient layer.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `FadeOut` | `Float` (default 2.0) | Fade-out duration in seconds |
- **Blueprint Authority:** Any
#### `SetMusicLayer` → `void`
- **Description:** Set a music layer with intensity parameter.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `LayerIndex` | `Integer` | Which music layer (0-3: Calm/Tense/Action/Climax) |
| `Sound` | `UMetaSoundSource` | MetaSound source for this layer |
| `Intensity` | `Float` (default 0.5) | Layer blend intensity 0-1 |
- **Blueprint Authority:** Any
#### `StopMusicLayers` → `void`
- **Description:** Fade out all music layers.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `FadeOut` | `Float` (default 2.0) | Fade-out duration in seconds |
- **Blueprint Authority:** Any
#### `PlayMusicStinger` → `void`
- **Description:** One-shot music stinger on Music bus (overrides layers temporarily).
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Stinger` | `UMetaSoundSource` | Stinger MetaSound source |
- **Blueprint Authority:** Any
#### `PlayDialogue` → `void`
- **Description:** Play dialogue line. Ducks other buses by configured amounts.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `DialogueSound` | `UMetaSoundSource` | Dialogue MetaSound source |
| `Priority` | `Integer` (default 0) | 0=Bark, 10=Quest, 100=Cinematic |
- **Blueprint Authority:** Any
- **Flow:**
1. If `ActiveDialogueCount >= MaxConcurrentDialogue`, check priority
2. If new priority > lowest active: interrupt lowest, play new
3. Apply ducking: SFX -6dB, Ambience -12dB, Music -18dB
4. Route dialogue through Dialogue bus with spatialization
#### `StopDialogue` → `void`
- **Description:** Stop current dialogue playback.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `FadeOut` | `Float` (default 0.5) | Fade-out duration in seconds |
- **Blueprint Authority:** Any
### Public Room Zone Functions
#### `SetRoomPreset` → `void`
- **Description:** Begin room transition with smooth crossfade of reverb/occlusion parameters.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Preset` | `DA_RoomAcousticPreset` | The new room acoustic preset |
- **Blueprint Authority:** Any
- **Flow:**
1. Push current preset onto `RoomZoneStack`
2. Set `PreviousRoomPreset = ActiveRoomPreset`
3. Set `ActiveRoomPreset = Preset`
4. Set `RoomTransitionDuration = Preset.TransitionTime`
5. Set `bIsCrossfading = true`
6. Fire `OnRoomPresetChanged(PreviousPreset, Preset)`
7. Begin per-tick interpolation (see `TickRoomTransition`)
#### `SetRoomPresetInstant` → `void`
- **Description:** Instant room switch — no crossfade. Used for void space entry, cutscenes.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Preset` | `DA_RoomAcousticPreset` | The new room preset to apply immediately |
- **Blueprint Authority:** Any
#### `GetCurrentRoomPreset` → `DA_RoomAcousticPreset`
- **Description:** Returns the currently active room preset.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `PopRoomPreset` → `void`
- **Description:** Restores the previous room from the zone stack. Called by `BP_RoomAudioZone` on overlap end.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any
### Public Settings / Control Functions
#### `SetBusVolume` → `void`
- **Description:** Set volume for a specific bus category. Called from settings menu.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Bus` | `E_AudioBusCategory` | Which bus to adjust |
| `Volume` | `Float` (01) | Target volume |
- **Blueprint Authority:** Any
- **Flow:**
1. Set float parameter `Volume` on the corresponding MetaSound bus
2. Fire `OnBusVolumeChanged`
3. If `Bus == Master`, scale all buses proportionally
#### `GetBusVolume` → `Float`
- **Description:** Get current volume for a specific bus.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Bus` | `E_AudioBusCategory` | Which bus to query |
- **Blueprint Authority:** Any (Pure)
#### `SetMasterMute` → `void`
- **Description:** Mute/unmute all audio globally.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `bMuted` | `Boolean` | True to mute, false to unmute |
- **Blueprint Authority:** Any
#### `SetPauseAll` → `void`
- **Description:** Pause/resume all audio (used when pause menu opens/closes).
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `bPaused` | `Boolean` | True to pause, false to resume |
- **Blueprint Authority:** Any
- **Flow:**
1. Set `bIsPaused = bPaused`
2. Set Paused on all bus audio components
3. Fire `OnAudioPaused(bPaused)`
#### `UpdateGameplayParameter` → `void`
- **Description:** Set a gameplay-driven MetaSound float parameter on the relevant bus.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `ParameterName` | `FName` | MetaSound parameter name (e.g., `MusicIntensity`) |
| `Value` | `Float` | Parameter value |
- **Blueprint Authority:** Any
#### `UpdateHeartRateAudio` → `void`
- **Description:** Drives heartbeat SFX tempo and breathing layer intensity from StateManager data.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `BPM` | `Float` | Current heart rate in BPM (60-180) |
| `Signal` | `E_PlayerVitalSignals` | Vital signal tier |
- **Blueprint Authority:** Any
- **Flow:**
1. Set `HeartRateBPM` parameter on SFX bus
2. Set `VitalSignal` parameter (0-4 enum as int) on SFX bus
3. If heartbeat SFX not already playing: start looping heartbeat MetaSound
#### `UpdateFearAudio` → `void`
- **Description:** Drives fear-reactive audio modulation from StressSystem + FearSystem.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `FearLevel` | `Float` (01) | Current fear intensity |
| `Tier` | `E_StressTier` | Current stress tier |
- **Blueprint Authority:** Any
- **Flow:**
1. Set `FearIntensity` parameter on Ambience + SFX buses
2. Set `StressLevel` parameter on Ambience + Music buses
3. Apply fear effects: subtle pitch drop, reverb increase, random pan jitter
### Internal Functions (Private)
#### `InitializeBuses` → `void`
- **Description:** Create `UAudioComponent` for each bus category and attach MetaSound sources. Called during `Initialize`.
- **Flow:**
1. For each `BusConfig` in `BusConfigs`:
a. Create `UAudioComponent`
b. Set sound to `BusConfig.MetaSoundSource`
c. Configure spatialization from `BusConfig.bSpatialized`
d. Attach attenuation settings
e. Store in `BusInstances` map
2. Apply saved settings volumes to each bus
3. Set `ActiveRoomPreset = DefaultRoomPreset`
#### `TickRoomTransition` → `void`
- **Description:** Called on Tick — interpolates room reverb/occlusion/filter parameters during crossfade.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `DeltaTime` | `Float` | Frame time |
- **Flow:**
1. If `!bIsCrossfading`: return
2. `RoomTransitionProgress += DeltaTime`
3. If `RoomTransitionProgress >= RoomTransitionDuration`: clamp to 1.0, set `bIsCrossfading = false`
4. Alpha = `RoomTransitionProgress / RoomTransitionDuration`
5. Interpolate: ReverbDensity, ReverbDecayTime, ReverbWetLevel, ReflectionsDelay, RoomSizeScale, OcclusionMultiplier, LowPassCutoff between `PreviousRoomPreset` and `ActiveRoomPreset`
6. Apply to relevant buses based on `bApplyToSFX` / `bApplyToAmbience` / `bApplyToDialogue` flags
#### `GetOrCreateSFXInstance` → `UAudioComponent`
- **Description:** Pool management — reuse an idle SFX audio component or create a new one. If pool is full (32), cull the oldest instance.
- **Flow:**
1. Find first idle component in `ActiveSFXInstances`
2. If found: return it
3. If pool not full: create new, add to pool, return
4. If pool full: stop oldest active instance, reuse it
#### `ApplyRoomToBus` → `void`
- **Description:** Apply a room preset's reverb/occlusion/filter to a specific bus.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Bus` | `E_AudioBusCategory` | Target bus |
| `Preset` | `DA_RoomAcousticPreset` | Room preset to apply |
#### `ApplySettingsToBus` → `void`
- **Description:** Read saved settings volume from `SS_SettingsSystem` and apply to the bus component.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Bus` | `E_AudioBusCategory` | Target bus |
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnRoomPresetChanged` | `OldPreset: FName`, `NewPreset: FName` | `Public` | Fired when room acoustic profile changes |
| `OnBusVolumeChanged` | `Bus: E_AudioBusCategory`, `Volume: Float` | `Public` | Fired when a bus volume is adjusted |
| `OnAudioPaused` | `bPaused: Boolean` | `Public` | Fired on pause/resume — for visual feedback |
| `OnMasterMuteChanged` | `bMuted: Boolean` | `Public` | Fired when global mute toggles |
| `OnDialogueStarted` | `DialogueSound: UMetaSoundSource`, `Priority: Integer` | `Public` | Fired when dialogue begins (for subtitle triggering) |
| `OnDialogueEnded` | `WasInterrupted: Boolean` | `Public` | Fired when dialogue ends |
---
## 6. Overridden Events / Custom Events
### Event: `Initialize`
- **Description:** Called when subsystem is created. Loads config, initializes buses, binds settings.
- **Flow:**
1. Load `DA_AudioSettings` if assigned
2. Call `InitializeBuses()`
3. Bind to `SS_SettingsSystem.OnSettingChanged` → route volume changes to `SetBusVolume`
4. Enable tick (required for room transition interpolation)
5. Apply saved settings volumes from persistent storage
### Event: `Deinitialize`
- **Description:** Cleanup all audio components and unbind delegates.
- **Flow:**
1. Stop all bus audio components
2. Clear `ActiveSFXInstances` pool
3. Unbind from `SS_SettingsSystem`
4. Set all references to null
---
## 7. Blueprint Graph Logic Flow
### Audio Playback Routing
```mermaid
flowchart TD
A[Any System: PlaySound] --> B[SS_AudioManager.PlaySFX / PlayAmbient / PlayMusic / PlayDialogue]
B --> C{Route by Category}
C -->|SFX| D[SFX Bus MetaSound Source]
C -->|Ambience| E[Ambience Bus MetaSound Source]
C -->|Music| F[Music Bus MetaSound Source]
C -->|Dialogue| G[Dialogue Bus MetaSound Source]
D --> H[Room Zone Reverb]
E --> H
F --> H
G --> H
H --> I[Master Bus]
I --> J[Player Settings Volume Sliders]
J --> K[Audio Output]
L[BPC_StateManager.HeartRate] -->|Parameter| D
M[BPC_StressSystem.StressTier] -->|Parameter| E
N[BPC_PacingDirector.IntensityBand] -->|Parameter| F
O[BPC_AccessibilitySettings] -->|Volume Sliders| I
```
### Settings Flow
```mermaid
sequenceDiagram
participant SM as WBP_SettingsMenu
participant SS as SS_SettingsSystem
participant AM as SS_AudioManager
participant Bus as MetaSound Bus
SM->>SS: SetFloat(Settings.Audio.SFXVolume, 0.8)
SS->>SS: Save to persistent settings
SS->>AM: SetBusVolume(SFX, 0.8)
AM->>Bus: Set Float Parameter 'Volume' = 0.8
Bus->>Bus: Apply to output
Note over AM: On game launch:
AM->>SS: GetFloat(Settings.Audio.SFXVolume)
SS->>AM: 0.8
AM->>Bus: Set Float Parameter 'Volume' = 0.8
```
---
## 8. Mix Bus Architecture
```
┌─────────────────────────────────────────────────────┐
│ MASTER BUS │
│ (MS_MasterBus) │
│ - Master Volume (settings) │
│ - Limiter / Compressor │
│ - Global Mute │
│ - Accessibility: Mono collapse, L/R balance │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ SFX BUS (MS_SFXBus) │ │
│ │ - 3D Spatialization │ │
│ │ - Per-source occlusion (line trace) │ │
│ │ - Distance attenuation │ │
│ │ - Surface material EQ (concrete/metal/wood) │ │
│ │ - Heart rate BPM → breathing blend param │ │
│ │ - Max 32 concurrent voices │ │
│ │ - Ducking: -6dB when Dialogue active │ │
│ │ Sub-inputs: │ │
│ │ • WeaponFire (limit 8 voices) │ │
│ │ • Footsteps (limit 4 voices per actor) │ │
│ │ • Impacts (limit 8 voices) │ │
│ │ • UI SFX (2D, no spatialization) │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ AMBIENCE BUS (MS_AmbientBus) │ │
│ │ - Stereo (no 3D) │ │
│ │ - 4-layer crossfade (room tone, wind, drone) │ │
│ │ - Stress tier → layer blend weights │ │
│ │ - Room reverb applied │ │
│ │ - Ducking: -12dB when Dialogue active │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ MUSIC BUS (MS_MusicBus) │ │
│ │ - Stereo (no 3D) │ │
│ │ - 4 intensity layers (Calm/Tense/Action/Climax)│ │
│ │ - PacingDirector.IntensityBand → layer blend │ │
│ │ - Stingers (one-shot overrides) │ │
│ │ - Ducking: -18dB when Dialogue active │ │
│ │ - Combat detected → crossfade to Action │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ DIALOGUE BUS (MS_DialogueBus) │ │
│ │ - 3D (for in-world dialogue) │ │
│ │ - Priority system (Cinematic > Quest > Bark) │ │
│ │ - Ducks all other buses │ │
│ │ - Lip-sync amplitude passthrough │ │
│ │ - Radio/phone EQ preset toggle │ │
│ │ - Max 2 concurrent dialogue voices │ │
│ │ - Settings: DialogueVolume → bus volume │ │
│ └──────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────┘
```
---
## 9. Settings Integration
### Player-Facing Volume Sliders
| Setting | GameplayTag | Bus Controlled | MetaSound Parameter |
|---------|------------|----------------|---------------------|
| Master Volume | `Settings.Audio.MasterVolume` | Master Bus | `Volume` (01) |
| SFX Volume | `Settings.Audio.SFXVolume` | SFX Bus | `Volume` (01) |
| Music Volume | `Settings.Audio.MusicVolume` | Music Bus | `Volume` (01) |
| Ambience Volume | `Settings.Audio.AmbientVolume` | Ambience Bus | `Volume` (01) |
| Dialogue Volume | `Settings.Audio.DialogueVolume` | Dialogue Bus | `Volume` (01) |
### Accessibility Settings
| Setting | Effect |
|---------|--------|
| Mono Audio | Collapse all buses to mono on Master Bus |
| Subtitle Background Opacity | (UI — not audio, handled by WBP_AccessibilityUI) |
| Audio Cues for UI | Play additional confirmation SFX on button press |
| Left/Right Balance | Pan parameter on Master Bus (for single-ear players) |
| Visual Heartbeat Indicator | Alternative to audio heartbeat for deaf players — UI pulse based on `BPC_StateManager.CurrentHeartRate` |
---
## 10. Gameplay Parameter Modulation
MetaSound parameters driven by gameplay state:
| Parameter Name | Source System | Bus | Range | Effect |
|---------------|--------------|-----|-------|--------|
| `HeartRateBPM` | `BPC_StateManager` (130) | SFX | 60180 | Controls heartbeat SFX tempo + breathing layer intensity |
| `VitalSignal` | `BPC_StateManager` (130) | SFX | 04 (enum) | Selects heartbeat sound variant (normal/elevated/critical/erratic) |
| `StressLevel` | `BPC_StressSystem` (10) | Ambience, Music | 01 | Blends tension layers, adds distortion |
| `FearIntensity` | `BPC_FearSystem` (90) | Ambience, SFX | 01 | Lowers pitch, increases reverb, random pan jitter |
| `MusicIntensity` | `BPC_PacingDirector` (98) | Music | 01 (4 bands) | Crossfades between Calm/Tense/Action/Climax layers |
| `PlayerHealth` | `BPC_HealthSystem` (08) | SFX | 01 | Low-pass filter on all SFX as health drops (muffled hearing) |
| `IsUnderwater` | (Physics volume) | Master | bool | Apply underwater EQ + muffled low-pass |
| `CombatActive` | `BPC_StateManager` (130) | Music | bool | Stinger trigger + immediate intensity ramp |
| `TimeOfDay` | World state | Ambience | 01 (dawn→dusk) | Ambient layer blend (day birds → night crickets) |
---
## 11. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| Any System | `SS_AudioManager.PlaySFX()` | `UMetaSoundSource`, `FVector Location`, `GameplayTag SurfaceTag` |
| Any System | `SS_AudioManager.PlaySFX2D()` | `UMetaSoundSource` (UI sounds) |
| Any System | `SS_AudioManager.PlayAmbient()` | `UMetaSoundSource`, `Float FadeIn` |
| Any System | `SS_AudioManager.SetMusicLayer()` | `Integer LayerIndex`, `UMetaSoundSource`, `Float Intensity` |
| Any System | `SS_AudioManager.PlayDialogue()` | `UMetaSoundSource`, `Integer Priority` |
| `BP_RoomAudioZone` (133) | `SS_AudioManager.SetRoomPreset()` | `DA_RoomAcousticPreset` |
| `WBP_SettingsMenu` (57) | `SS_SettingsSystem.SetFloat()``SS_AudioManager.SetBusVolume()` | `E_AudioBusCategory`, `Float Volume` |
| `BPC_StateManager` (130) | `SS_AudioManager.UpdateHeartRateAudio()` | `Float BPM`, `E_PlayerVitalSignals` |
| `BPC_StressSystem` (10) | `SS_AudioManager.UpdateFearAudio()` | `Float FearLevel`, `E_StressTier` |
| `BPC_PacingDirector` (98) | `SS_AudioManager.UpdateGameplayParameter("MusicIntensity")` | `Float 01` |
| `BPC_HealthSystem` (08) | `SS_AudioManager.UpdateGameplayParameter("PlayerHealth")` | `Float 01` |
| `SS_AudioManager` | `Dispatcher: OnRoomPresetChanged` | `OldPreset: FName`, `NewPreset: FName` |
| `SS_AudioManager` | `Dispatcher: OnBusVolumeChanged` | `Bus: E_AudioBusCategory`, `Volume: Float` |
| `SS_AudioManager` | `Dispatcher: OnAudioPaused` | `bPaused: Boolean` |
| `SS_AudioManager` | `Dispatcher: OnDialogueStarted` | `UMetaSoundSource`, `Integer Priority` (for subtitle system) |
| `SS_AudioManager` | `WBP_AccessibilityUI` (45) | Heart rate BPM for visual heartbeat indicator |
---
## 12. Migration: Replacing Deprecated BPC_AudioAtmosphereController (95)
| Old (BPC_AudioAtmosphereController) | New (SS_AudioManager) |
|-------------------------------------|----------------------|
| `PlayOneShotSFX(Sound, Location)` | `SS_AudioManager.PlaySFX(Sound, Location)` |
| `PlayAmbientSound(Sound, FadeIn)` | `SS_AudioManager.PlayAmbient(Sound, FadeIn)` |
| `SetMusicLayer(LayerIndex, Sound, Volume)` | `SS_AudioManager.SetMusicLayer(LayerIndex, Sound, Intensity)` |
| `PlayDialogue(DialogueSound)` | `SS_AudioManager.PlayDialogue(DialogueSound, Priority)` |
| `SetReverb(Reverb, WetLevel)` | `SS_AudioManager.SetRoomPreset(Preset)` via `BP_RoomAudioZone` |
| `SetOcclusion(Intensity)` | Automatic — per-source line trace in MetaSound bus |
| `FearReactiveAudioUpdate(Fear, Threshold)` | `SS_AudioManager.UpdateFearAudio(Fear, Tier)` |
| `HandleRoomChange(RoomAcousticPreset)` | `BP_RoomAudioZone.OnActorBeginOverlap` |
| `ActiveAudioPreset` / `TargetAudioPreset` | `SS_AudioManager.ActiveRoomPreset` |
| `Master/SFX/Music/Ambient/DialogueVolumeModifier` | `SS_AudioManager.SetBusVolume()` bound to `SS_SettingsSystem` |
### Phase-Out Strategy
1. **Phase 14a:** Create `SS_AudioManager` + `BP_RoomAudioZone` + all MetaSound patches (CURRENT)
2. **Phase 14b:** Update all 129 systems to call `SS_AudioManager` instead of raw `UGameplayStatics::PlaySound*`
3. **Phase 14c:** Convert all `USoundBase` references to `UMetaSoundSource` in Data Assets
4. **Phase 14d:** Remove `BPC_AudioAtmosphereController` (95) from build
---
## 13. Validation / Testing Checklist
- [ ] `PlaySFX(Sound, Location)` spawns at correct world position with spatialization
- [ ] `PlaySFX2D(Sound)` plays without spatialization (UI sounds)
- [ ] `PlayAmbient(Sound)` crossfades ambient layer smoothly
- [ ] `SetMusicLayer(idx, Sound, intensity)` blends music layers correctly
- [ ] `PlayDialogue(Sound, Priority)` ducks other buses by correct amounts
- [ ] `BP_RoomAudioZone` on overlap → `SetRoomPreset` transitions reverb smoothly
- [ ] Nested room zones use stack correctly (push/pop)
- [ ] Room transition crossfade interpolates reverb parameters
- [ ] `SS_SettingsSystem` volume changes apply to correct bus
- [ ] Master Mute silences all buses
- [ ] Pause/Resume toggles all bus audio components
- [ ] `UpdateHeartRateAudio(BPM)` modulates heartbeat tempo in MetaSound
- [ ] `UpdateFearAudio(0.8, Terrified)` applies fear distortion
- [ ] SFX pooling limits to 32 concurrent (oldest culled when full)
- [ ] Dialogue priority system: higher priority interrupts lower
- [ ] Accessibility mono audio collapses all buses
- [ ] Accessibility L/R balance pans correctly on Master Bus
- [ ] Edge case: Rapid room zone enter/exit doesn't cause audio glitches
- [ ] Edge case: SS_AudioManager handles null MetaSound source gracefully
- [ ] Edge case: Settings loaded before first sound plays (Init order)
- [ ] Edge case: `SetMasterMute(true)` while crossfading — immediate mute
---
## 14. Reuse Notes
- `SS_AudioManager` is the **ONLY** entry point for audio playback. Systems must never call `UGameplayStatics::PlaySound*` directly.
- Game instance subsystem — exists for the entire game session. No per-level reinitialization needed.
- SFX pool of 32 ensures no unbounded audio component creation. Reuse is automatic.
- Room zone stack supports arbitrarily nested zones (room within building within cave).
- All gameplay parameters are soft-referenced — if a source system (e.g., `BPC_PacingDirector`) is not present, `UpdateGameplayParameter` fails gracefully (log warning, no crash).
- MetaSound sources can be hot-reloaded in-editor while the game is running for rapid sound design iteration.
---
*Blueprint Spec: SS_AudioManager — Single entry point for all MetaSounds audio. Replaces BPC_AudioAtmosphereController (95). Architecture document: [`metasounds-audio-system.md`](../../architecture/metasounds-audio-system.md)*

View File

@@ -0,0 +1,216 @@
# 133 — Room Audio Zone (`BP_RoomAudioZone`)
## Purpose
A trigger volume placed in the world that automatically switches the audio reverb/occlusion profile when the player enters. When the player exits, it restores the previous room preset via a push/pop stack in `SS_AudioManager`. Supports nested room zones (room within building within cave) with priority-based override.
## Dependencies
- **Requires:** `SS_AudioManager` (132 — calls `SetRoomPreset` / `PopRoomPreset` on overlap), `DA_RoomAcousticPreset` (135 — assigned acoustic profile)
- **Required By:** Level Designers (placed in world)
- **Engine/Plugin Requirements:** MetaSounds plugin (for room reverb pipeline)
## Class Info
| Property | Value |
|----------|-------|
| **Parent Class** | `TriggerVolume` (or `Actor` with `BoxComponent`) |
| **Class Type** | Blueprint Actor |
| **Asset Path** | `Content/Framework/Audio/BP_RoomAudioZone.uasset` |
| **Implements Interfaces** | *(none)* |
---
## 1. Enums
*Uses enums defined elsewhere:*
- `E_AudioBusCategory` — from `SS_AudioManager` (132)
---
## 2. Structs
*Uses struct defined in `SS_AudioManager` (132):*
- `S_RoomAcousticProfile` — acoustic profile fields (reverb density, decay time, wet level, etc.)
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `RoomPreset` | `DA_RoomAcousticPreset` | `None` | `Room Config` | Acoustic profile for this room |
| `TransitionTime` | `Float` | `1.5` | `Room Config` | Crossfade duration on enter (seconds) |
| `ExitTransitionTime` | `Float` | `1.0` | `Room Config` | Crossfade duration on exit (seconds) |
| `bOverrideOcclusion` | `Boolean` | `false` | `Room Config` | Override occlusion settings in this zone |
| `OcclusionTraceChannel` | `ETraceTypeQuery` | `Camera` | `Room Config` | Trace channel for per-source occlusion |
| `Priority` | `Integer` | `0` | `Room Config` | Higher priority zones override lower (for nested rooms) |
| `bIsActive` | `Boolean` | `true` | `State` | Whether this room zone is actively processing |
| `ZoneDebugColor` | `LinearColor` | `Cyan` | `Debug` | Color of the trigger volume in editor for visual identification |
| `bShowDebugInfo` | `Boolean` | `false` | `Debug` | Print room zone enter/exit to log |
### Internal (Private)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `bPlayerInside` | `Boolean` | `false` | `State` | Whether the player is currently inside this zone |
---
## 4. Functions
### Public Functions
#### `SetRoomAudioEnabled` → `void`
- **Description:** Toggle room audio processing. Used for narrative lock, void space override.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `bEnabled` | `Boolean` | True to enable, false to disable zone processing |
- **Blueprint Authority:** Any
- **Flow:**
1. Set `bIsActive = bEnabled`
2. If `bEnabled == false` and `bPlayerInside`: force `PopRoomPreset` on `SS_AudioManager`
3. If `bEnabled == true` and `bPlayerInside`: re-apply `SetRoomPreset`
#### `GetPriority` → `Integer`
- **Description:** Returns the zone priority. Used by `SS_AudioManager` for nested zone resolution.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
#### `GetAcousticProfile` → `DA_RoomAcousticPreset`
- **Description:** Returns the assigned room acoustic preset.
- **Parameters:** *(none)*
- **Blueprint Authority:** Any (Pure)
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnPlayerEnteredZone` | `ZoneName: FName` | `Public` | Fired when player enters (for gameplay logic) |
| `OnPlayerExitedZone` | `ZoneName: FName` | `Public` | Fired when player exits |
| `OnZoneEnabledChanged` | `bEnabled: Boolean` | `Public` | Fired when `SetRoomAudioEnabled` is called |
---
## 6. Overridden Events / Custom Events
### Event: `OnActorBeginOverlap`
- **Description:** Detects player character entering the trigger volume → calls `SS_AudioManager.SetRoomPreset()`.
- **Flow:**
1. Validate `OtherActor` — is it the player character? (check tag `Player` or cast to `BP_PlayerCharacter`)
2. If not player: return
3. If `!bIsActive`: return
4. Set `bPlayerInside = true`
5. Get `SS_AudioManager` from Game Instance
6. Call `SS_AudioManager.SetRoomPreset(RoomPreset)` with `TransitionTime`
7. Fire `OnPlayerEnteredZone`
8. If `bShowDebugInfo`: log zone name to console
### Event: `OnActorEndOverlap`
- **Description:** Player exits → restore previous room preset via `SS_AudioManager.PopRoomPreset()`.
- **Flow:**
1. Validate `OtherActor` — is it the player?
2. If not player: return
3. If `!bIsActive`: return
4. Set `bPlayerInside = false`
5. Get `SS_AudioManager` from Game Instance
6. Call `SS_AudioManager.PopRoomPreset()` (restores previous room from stack)
7. Fire `OnPlayerExitedZone`
8. If `bShowDebugInfo`: log to console
### Event: `BeginPlay`
- **Description:** Register this zone with a global zone manager if one exists. Configure trigger volume collision settings.
- **Flow:**
1. Configure trigger volume: `SetCollisionResponseToChannel(ECC_Pawn, ECR_Overlap)`
2. Set `Generate Overlap Events = true`
3. Optionally set trigger volume color from `ZoneDebugColor`
4. If `RoomPreset` is null: log warning, set `bIsActive = false`
### Event: `EndPlay`
- **Description:** Cleanup — if player is inside zone on destruction, force pop the room preset.
- **Flow:**
1. If `bPlayerInside`: call `SS_AudioManager.PopRoomPreset()`
2. Set `bPlayerInside = false`
---
## 7. Blueprint Graph Logic Flow
### Nested Room Zone Stack
```mermaid
flowchart TD
A[Enter Room A - SmallRoom] --> B[SS_AudioManager.SetRoomPreset: SmallRoom]
B --> C[Enter Room B - Bathroom, Priority > A]
C --> D[SS_AudioManager.SetRoomPreset: Bathroom]
D --> E[Exit Room B]
E --> F[SS_AudioManager.PopRoomPreset → Restore SmallRoom]
F --> G[Exit Room A]
G --> H[SS_AudioManager.PopRoomPreset → Restore Default/Outdoor]
```
---
## 8. Room Preset Reference
### Included Room Presets (in `Content/Framework/Audio/RoomPresets/`)
| Preset Asset | Description | Typical Use |
|-------------|-------------|-------------|
| `DA_RoomAcousticPreset` | Base class | Parent for all presets |
| `DA_RP_SmallRoom` | Small room reverb | Offices, bedrooms, closets |
| `DA_RP_LargeHall` | Large hall reverb | Auditoriums, churches, lobbies |
| `DA_RP_Outdoor` | Outdoor (minimal reverb) | Default fallback, streets, forests |
| `DA_RP_Cave` | Cave/underground reverb | Caves, tunnels, basements |
| `DA_RP_VoidSpace` | Void/alt death space reverb | Alt death space, dream sequences |
| `DA_RP_Industrial` | Industrial/factory reverb | Factories, warehouses, hangars |
| `DA_RP_Bathroom` | Small tile reverb | Bathrooms, tiled corridors |
---
## 9. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| `BP_RoomAudioZone` | `SS_AudioManager.SetRoomPreset()` | `DA_RoomAcousticPreset`, crossfade time |
| `BP_RoomAudioZone` | `SS_AudioManager.PopRoomPreset()` | *(none — restores previous from stack)* |
| `BP_RoomAudioZone` | `Dispatcher: OnPlayerEnteredZone` | `FName ZoneName` (for gameplay scripting) |
| `BP_RoomAudioZone` | `Dispatcher: OnPlayerExitedZone` | `FName ZoneName` |
| Level Designer | Direct placement in world | Adjusts `RoomPreset`, `Priority`, `TransitionTime` in Details Panel |
| Narrative System | `SetRoomAudioEnabled(false)` | Disables zone during cutscenes/void space |
---
## 10. Validation / Testing Checklist
- [ ] Player entering zone triggers `SetRoomPreset` with correct preset
- [ ] Reverb parameters smoothly crossfade over `TransitionTime`
- [ ] Player exiting zone triggers `PopRoomPreset` restoring previous room
- [ ] Nested zones: entering inner room, exiting restores outer room
- [ ] Priority: higher priority zone overrides lower priority when overlapping
- [ ] `SetRoomAudioEnabled(false)` during overlap — zone is disabled
- [ ] `SetRoomAudioEnabled(true)` after disable — zone reapplies
- [ ] Non-player actors do NOT trigger zone overlap
- [ ] Zone with null `RoomPreset` logs warning and remains inactive
- [ ] `bShowDebugInfo` logs enter/exit to console
- [ ] Zone destroyed while player inside — `PopRoomPreset` still called
- [ ] Edge case: Rapid enter/exit (player on boundary) — no audio glitching
- [ ] Edge case: `SS_AudioManager` not found — zone gracefully handles missing subsystem
- [ ] Edge case: Multiple zones with same `Priority` — last-entered wins
---
## 11. Reuse Notes
- Place multiple `BP_RoomAudioZone` actors throughout a level to create realistic acoustic transitions.
- Use `Priority` to handle overlapping zones — higher values override. Example: a bathroom (Priority=5) inside a house (Priority=0).
- `bOverrideOcclusion` enables per-source occlusion line traces in this zone — useful for walls, doors.
- The `OnPlayerEnteredZone` / `OnPlayerExitedZone` dispatchers allow gameplay logic to react to room changes (e.g., "You've entered the forbidden wing" audio sting).
- Zone scaling: Box components can be scaled to any size. Use multiple zones for irregularly shaped rooms.
- Debug mode (`bShowDebugInfo` + `ZoneDebugColor`) helps level designers visualize acoustic boundaries.
---
*Blueprint Spec: BP_RoomAudioZone — Trigger volume for automatic room acoustic switching. Architecture document: [`metasounds-audio-system.md`](../../architecture/metasounds-audio-system.md) Section 6.*

View File

@@ -0,0 +1,137 @@
# 62 — BPC_DifficultyManager
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Central game instance access
- [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Input for difficulty tuning
- [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) — Output to atmosphere/scare systems
- [`BPC_HealthSystem`](../02-player/08_BPC_HealthSystem.md) — Health/revive tuning
- [`BPC_InventoryComponent`](../04-inventory/22_BPC_InventoryComponent.md) — Resource scarcity input
### Purpose
Central difficulty orchestrator. Dynamically adjusts game challenge based on player performance metrics and explicit difficulty profile settings. Operates as a non-intrusive governor that tunes encounter intensity, resource availability, AI aggression, puzzle complexity, and survival pressure without direct player awareness (unless overridden by explicit difficulty mode selection).
### Variables
| Name | Type | Description |
|------|------|-------------|
| `CurrentDifficulty` | EGameDifficulty | Active difficulty preset |
| `bIsAdaptive` | Bool | Auto-adjust based on performance |
| `AdaptiveBias` | Float (0.01.0) | Current adaptive tuning bias |
| `PlayerEfficiencyScore` | Float (0.01.0) | Rolling efficiency metric |
| `PlayerSurvivalScore` | Float (0.01.0) | Rolling survival metric |
| `ExplorationThoroughness` | Float (0.01.0) | How much player explores |
| `ResourceConsumptionRate` | Float | Scarcity/frequency of resources |
| `bIsPaused` | Bool | Pause all difficulty adjustments |
| `AdjustmentCooldown` | Float | Seconds between recalibrations |
| `LastAdjustmentTime` | Float | Timestamp of last adjustment |
| `PerformanceWindow` | TArray\<Float\> | Sliding window of recent metric samples |
| `MaxWindowSize` | Int | Max samples before averaging |
### Enums
| Enum | Values | Description |
|------|--------|-------------|
| `EGameDifficulty` | Story, Easy, Normal, Hard, Survival, Adaptive | Difficulty presets |
### Structs
| Struct | Fields | Description |
|--------|--------|-------------|
| `FDifficultyParams` | HealthMultiplier, DamageMultiplier, AmmoScarcity, EnemyAggression, PuzzleComplexity, FearIntensity, ReviveCount, CheckpointFrequency, TimedPressure | Tuning package |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | Difficulty: EGameDifficulty | — | Set initial difficulty state |
| `SetDifficulty` | Difficulty: EGameDifficulty | — | Apply explicit difficulty preset |
| `SetAdaptiveMode` | bEnabled: Bool | — | Toggle adaptive adjustment |
| `RecalculateDifficulty` | — | — | Analyze metrics and adjust |
| `GetCurrentParams` | — | FDifficultyParams | Return current tuning |
| `ApplyHealthMultiplier` | BaseValue: Float | Float | Scaled health |
| `ApplyDamageMultiplier` | BaseValue: Float | Float | Scaled damage |
| `GetAmmoScarcity` | — | Float | 0.0 = abundant, 1.0 = scarce |
| `GetEnemyAggressionModifier` | — | Float | Multiplier for AI aggression |
| `GetFearIntensityModifier` | — | Float | Scare system intensity |
| `GetReviveLimit` | — | Int | Max revives allowed |
| `GetCheckpointFrequency` | — | Float | Time between auto-saves |
| `IsTimedPressureActive` | — | Bool | Survival timer enabled |
| `PauseAdjustments` | bPaused: Bool | — | Freeze recalibration |
### Event Dispatchers
| Name | Parameters | Fired When |
|------|-----------|-----------|
| `OnDifficultyChanged` | NewDifficulty: EGameDifficulty | Difficulty preset changes |
| `OnAdaptiveShift` | ShiftAmount: Float | Adaptive tuning adjusts |
| `OnPerformanceWarning` | Metric: EPerformanceMetric, Value: Float | Player struggling/excelling |
### Blueprint Flow
```
[Initialize]
└─► Set default difficulty based on game mode / save data
└─► SetDifficulty(DefaultDifficulty)
└─► If bIsAdaptive:
Start recalibration timer (AdjustmentCooldown)
Bind to BPC_PlayerMetricsTracker dispatchers
[RecalculateDifficulty — called by timer or metric threshold]
└─► Gather metrics from BPC_PlayerMetricsTracker:
DeathCount, HealingUsed, AmmoUsed, EnemiesKilled,
TimeInCombat, TimeHidden, PuzzlesSolved
└─► Compute PlayerEfficiencyScore =
(EnemiesKilled / TimeInCombat) * ExplorationThoroughness
└─► Compute PlayerSurvivalScore =
1.0 - (DeathCount / MaxDeathsBeforeAdjust)
└─► If PlayerSurvivalScore < LowThreshold:
Reduce enemy aggression by 0.1
Increase ammo drops by 15%
Extend checkpoint frequency
Fire OnPerformanceWarning(Struggling)
└─► If PlayerEfficiencyScore > HighThreshold AND DeathCount == 0:
Increase enemy aggression by 0.1
Reduce ammo drops by 10%
Shorten checkpoint frequency
Fire OnPerformanceWarning(Excelling)
└─► If bIsAdaptive:
Lerp AdaptiveBias toward target
BPC_AtmosphereController.SetTuningBias(AdaptiveBias)
└─► Update LastAdjustmentTime
└─► Broadcast OnAdaptiveShift
[SetDifficulty — explicit override]
└─► Set CurrentDifficulty = input
└─► bIsAdaptive = false if explicit non-Adaptive difficulty
└─► Broadcast OnDifficultyChanged
└─► Apply to dependent systems:
BPC_HealthSystem.MaxRevives = GetReviveLimit()
SS_SaveManager.AutoSaveInterval = GetCheckpointFrequency()
BPC_AtmosphereController.FearIntensityBias = GetFearIntensityModifier()
```
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Get Owner Component | Feed performance data |
| [`BPC_AtmosphereController`](65_BPC_AtmosphereController.md) | Direct call | Apply fear/intensity tuning |
| [`BPC_HealthSystem`](../02-player/08_BPC_HealthSystem.md) | Get Owner Component | Revive limits |
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Game Instance | Checkpoint frequency |
| [`BPC_InventoryComponent`](../04-inventory/22_BPC_InventoryComponent.md) | Get Owner Component | Resource scarcity input |
| [`BPC_FearSystem`](90_BPC_FearSystem.md) | Direct call | Fear intensity modifier |
| [`BPC_AlertSystem`](../09-ai/60_BPC_AlertSystem.md) | Get on AI Controller | Enemy aggression modifier |
### Reuse Notes
- Single instance per game (attach to GameMode or persistent GameState)
- Designed to be optional; game works without it using static defaults
- Adaptive mode is additive; explicit difficulty modes override everything
- Performance window smooths out spikes before adjustment triggers
- All tuning values are configurable at runtime via console or debug menu

View File

@@ -0,0 +1,182 @@
# 63 — BPC_FearSystem
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Central game instance access
- [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player anxiety/stress metrics
- [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) — Fear intensity modifier
- [`BPC_StressSystem`](../02-player/10_BPC_StressSystem.md) — Player stress level
- [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) — Atmosphere response
- [`BPC_AudioManager`](66_BPC_AudioManager.md) — Audio feedback
- [`BPC_VFXManager`](67_BPC_VFXManager.md) — Visual feedback
- [`BPC_LightingManager`](65_BPC_LightingManager.md) — Lighting response
- [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) — Story-aware fear triggers
### Purpose
Core fear and anxiety system that manages the player's fear level, fear sources, fear decay, and fear-induced gameplay effects. Acts as the central orchestrator for all horror-related feedback loops — audio, visual, AI behavior, and UI — driven by the player's current fear state and environmental context.
### Variables
| Name | Type | Description |
|------|------|-------------|
| `CurrentFearLevel` | Float (0100) | Current fear value |
| `MaxFearLevel` | Float | Cap for fear (default 100) |
| `FearDecayRate` | Float | Passive fear reduction per second |
| `FearThresholds` | TMap\<EFearThreshold, Float\> | Threshold values for each fear tier |
| `ActiveFearSources` | TArray\<FFearSource\> | Currently contributing fear sources |
| `FearMultiplier` | Float | Global multiplier (from DifficultyManager) |
| `bIsInPanicState` | Bool | Overwhelmed by fear |
| `PanicDuration` | Float | Seconds of panic state |
| `PanicTimer` | Float | Panic countdown |
| `LastIncreaseTime` | Float | When fear last increased |
| `bFearDecayPaused` | Bool | Freeze decay |
| `EnvironmentalFearModifier` | Float | Ambient fear from atmosphere |
| `DarknessFearMultiplier` | Float | Extra fear in dark areas |
| `IsolatedFearMultiplier` | Float | Extra fear when alone |
| `ProximityFearMultiplier` | Float | Extra fear from nearby threats |
| `bIsInSafeZone` | Bool | Safe zone overrides fear |
### Enums
| Enum | Values | Description |
|------|--------|-------------|
| `EFearThreshold` | Calm, Uneasy, Nervous, Afraid, Terrified, Panic | Fear tiers |
### Structs
| Struct | Fields | Description |
|--------|--------|-------------|
| `FFearSource` | SourceName: FName, BaseValue: Float, CurrentValue: Float, DecayDelay: Float, bIsDecaying: Bool, TimeSinceTrigger: Float, Tag: FGameplayTag | Active fear contributor |
| `FFearEvent` | Source: FName, Value: Float, Duration: Float, bInstant: Bool, bStack: Bool, AssociatedTag: FGameplayTag | Incoming fear impulse |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Setup thresholds, bind events |
| `AddFear` | Event: FFearEvent | — | Add fear from any source |
| `RemoveFearSource` | SourceName: FName | — | Remove specific fear source |
| `ClearAllFear` | — | — | Reset fear to baseline |
| `GetFearLevel` | — | Float | Current fear value |
| `GetFearThreshold` | — | EFearThreshold | Current fear tier |
| `IsInPanic` | — | Bool | Panic state check |
| `SetFearDecayRate` | NewRate: Float | — | Override decay speed |
| `PauseFearDecay` | bPaused: Bool | — | Freeze/unfreeze decay |
| `UpdateEnvironmentalFear` | Modifier: Float | — | Ambient fear contribution |
| `CalculateFearTier` | FearValue: Float | EFearThreshold | Determine current tier |
| `TriggerPanic` | Duration: Float | — | Force panic state |
| `ResolvePanic` | — | — | End panic state early |
| `ApplyFearEffect` | Effect: EFearEffect | — | Gameplay consequences of fear |
### Event Dispatchers
| Name | Parameters | Fired When |
|------|-----------|-----------|
| `OnFearLevelChanged` | NewFear: Float, Threshold: EFearThreshold | Any fear change |
| `OnFearThresholdReached` | Threshold: EFearThreshold | Cross a fear tier boundary |
| `OnPanicStarted` | — | Panic state entered |
| `OnPanicEnded` | — | Panic state ended |
| `OnFearSourceAdded` | Source: FFearSource | New fear source registered |
| `OnFearSourceRemoved` | SourceName: FName | Fear source expired |
### Blueprint Flow
```
[Tick — every frame]
└─► If not bFearDecayPaused AND not bIsInPanicState:
CurrentFearLevel -= FearDecayRate * DeltaTime
Clamp CurrentFearLevel to 0..MaxFearLevel
└─► Process active fear sources:
For each FFearSource in ActiveFearSources:
Source.TimeSinceTrigger += DeltaTime
If Source.TimeSinceTrigger > Source.DecayDelay:
Source.bIsDecaying = true
Source.CurrentValue -= DecayRate * DeltaTime
If Source.CurrentValue <= 0:
Remove from ActiveFearSources
Fire OnFearSourceRemoved
└─► If bIsInPanicState:
PanicTimer -= DeltaTime
If PanicTimer <= 0:
ResolvePanic()
└─► CalculateFearTier(CurrentFearLevel)
└─► If tier changed since last frame:
Fire OnFearThresholdReached(NewThreshold)
[AddFear]
└─► Apply modifiers:
EffectiveValue = Event.Value * FearMultiplier
EffectiveValue *= EnvironmentalFearModifier
EffectiveValue *= DarknessFearModifier (if in dark volume)
EffectiveValue *= IsolatedFearModifier (if no allies)
EffectiveValue *= ProximityFearModifier (if enemies nearby)
└─► If Event.bStack:
Find existing source by Event.Source
If found: Stack.Value += EffectiveValue
Else: Create new FFearSource
└─► Else:
Create new FFearSource with Event parameters
└─► CurrentFearLevel += EffectiveValue
└─► Clamp to MaxFearLevel
└─► Check if panic threshold crossed:
If CurrentFearLevel >= FearThresholds[Panic]:
TriggerPanic(PanicDuration)
└─► Fire OnFearLevelChanged(CurrentFearLevel, CurrentThreshold)
└─► Fire OnFearSourceAdded(NewSource)
[TriggerPanic]
└─► bIsInPanicState = true
└─► PanicTimer = Duration (or PanicDuration default)
└─► ApplyFearEffect(EPanicEffects)
└─► BPC_AtmosphereController.TriggerPanicMode()
└─► BPC_AudioManager.PlayPanicAudio()
└─► BPC_VFXManager.TriggerPanicVFX()
└─► UI: Show panic overlay
└─► Fire OnPanicStarted
[ResolvePanic]
└─► bIsInPanicState = false
└─► CurrentFearLevel = FearThresholds[Terrified] * 0.8
└─► BPC_AtmosphereController.EndPanicMode()
└─► BPC_AudioManager.StopPanicAudio()
└─► Fire OnPanicEnded
[ApplyFearEffect — based on current threshold]
└─► EFearThreshold.Calm: No effects
└─► EFearThreshold.Uneasy: Subtle audio cues, slight camera sway
└─► EFearThreshold.Nervous: UI vignette, breathing sounds, reduced stamina regen
└─► EFearThreshold.Afraid: Visibility distortion, movement speed penalty, aim shake
└─► EFearThreshold.Terrified: Severe vignette, stammering audio, weapon sway
└─► EFearThreshold.Panic: Tunnel vision, audio distortion, sprint penalty, AI gets buffs
```
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Get Owner Component | Track fear history for metrics |
| [`BPC_StressSystem`](../02-player/10_BPC_StressSystem.md) | Get Owner Component | Stress/fear feedback loop |
| [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) | Get Owner Component | Fear intensity modifier |
| [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) | Direct call | Atmosphere panic mode |
| [`BPC_AudioManager`](66_BPC_AudioManager.md) | Direct call | Panic audio, fear ambience |
| [`BPC_VFXManager`](67_BPC_VFXManager.md) | Direct call | Visual effects per tier |
| [`BPC_LightingManager`](65_BPC_LightingManager.md) | Direct call | Dynamic lighting response |
| [`SS_UIManager`](../06-ui/32_SS_UIManager.md) | Subsystem | HUD fear indicator, panic overlay |
| [`BPC_NarrativeStateSystem`](../07-narrative/38_BPC_NarrativeStateSystem.md) | Get Owner Component | Story-aware fear modifiers |
| [`BPC_HealthSystem`](../02-player/08_BPC_HealthSystem.md) | Get Owner Component | Panic damage if health low |
| [`AI_EnemyControllers`](../09-ai/55_BPC_AIControllerBase.md) | Event Dispatcher | AI aggression rise with player fear |
### Reuse Notes
- Single instance on player character (or PlayerController)
- All fear sources are stackable and independently decayable
- Thresholds are designer-configurable at runtime
- Fear multiplier from DifficultyManager allows difficulty to scale horror
- Safe zones (checkpoints, safe rooms) pause fear accumulation and decay
- Panic state is a temporary debuff with full cleanup on resolve
- UI reads OnFearLevelChanged dispatcher, never polls

View File

@@ -0,0 +1,234 @@
# 69 — BPC_PerformanceScaler
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`BPC_LightingManager`](65_BPC_LightingManager.md) — Controls light quality
- [`BPC_AudioManager`](66_BPC_AudioManager.md) — Controls audio quality
- [`BPC_VFXManager`](67_BPC_VFXManager.md) — Controls particle LOD
- [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) — Preset complexity reduction
- [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) — Save performance settings
- [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) — Frame time measurement
### Purpose
Automatically adjusts graphics, audio, and gameplay quality settings based on real-time performance metrics (frame time, draw calls, memory usage). Provides a unified interface for all other systems to scale their quality without direct awareness of the hardware. Supports manual user override via Settings menu and adaptive automatic mode. Maintains a performance budget that is dynamically allocated across subsystems.
### Enums
**EPerformanceQualityLevel**
| Value | Description |
|-------|-------------|
| Low | Minimum quality, max performance |
| Medium | Balanced quality/performance |
| High | High quality |
| Ultra | Maximum quality, no compromises |
| Cinematic | Maximum visual fidelity, 30fps target |
**EScalingTarget**
| Value | Description |
|-------|-------------|
| ResolutionScale | Screen percentage |
| ShadowQuality | Shadow resolution and distance |
| TextureQuality | Texture streaming pool |
| PostProcessQuality | Post-process complexity |
| ParticleLOD | VFX particle count |
| AudioQuality | Audio layer count |
| ViewDistance | Draw distance |
| FoliageDensity | Foliage and grass density |
| AntiAliasing | Anti-aliasing method |
| GlobalIllumination | GI quality |
### Variables
| Name | Type | Description |
|------|------|-------------|
| `CurrentQualityLevel` | EPerformanceQualityLevel | Active quality level |
| `TargetQualityLevel` | EPerformanceQualityLevel | Quality being transitioned to |
| `bAdaptiveMode` | Bool | Auto-adjust based on performance |
| `bUserOverride` | Bool | Player manually set quality |
| `FrameTimeHistory` | TArray\<Float\> | Rolling frame time samples |
| `AverageFrameTime` | Float | Smoothed average frame time |
| `FrameTimeVariance` | Float | Frame time stability metric |
| `TargetFrameTime` | Float | Desired frame time (e.g., 16.67ms for 60fps) |
| `PerformanceBudget` | Float | Allowed ms per frame |
| `ScalerSettings` | TMap\<EScalingTarget, float\> | Current scaling multipliers |
| `bIsScalingInProgress` | Bool | Currently adjusting settings |
| `MeasurementInterval` | Float | Seconds between performance checks |
| `AdjustmentCooldown` | Float | Min time between quality changes |
| `LastAdjustmentTime` | Float | Last quality change timestamp |
| `QualityOverrideLevel` | EPerformanceQualityLevel | Manual override level |
| `PlatformProfile` | FName | Detected platform profile |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Read saved settings, detect platform |
| `SetQualityLevel` | Level: EPerformanceQualityLevel | — | Apply quality level |
| `SetAdaptiveMode` | bEnabled: Bool | — | Toggle adaptive scaling |
| `GetCurrentQualityLevel` | — | EPerformanceQualityLevel | Current level |
| `GetRecommendedQuality` | — | EPerformanceQualityLevel | System-recommended level |
| `MeasurePerformance` | — | — | Sample frame time from GI_GameFramework |
| `EvaluateScaling` | — | — | Decide if scaling is needed |
| `ApplyScalerSettings` | Level: EPerformanceQualityLevel | — | Apply settings to subsystems |
| `SetResolutionScale` | Scale: Float | — | Screen percentage |
| `SetShadowQuality` | Level: Int32 | — | Shadow resolution preset |
| `SetTexturePoolSize` | SizeMB: Int32 | — | Texture streaming pool |
| `SetViewDistance` | Distance: Float | — | Draw distance |
| `SetFoliageDensity` | Density: Float | — | Foliage cull distance |
| `SetParticleLOD` | LODLevel: Int32 | — | Particle LOD for VFXManager |
| `SetAudioQuality` | Quality: Int32 | — | Audio layer count |
| `SetGlobalIllumination` | Quality: Int32 | — | GI method |
| `NotifySubsystemScaled` | Target: EScalingTarget | — | Broadcast scaled subsystems |
| `SavePerformanceSettings` | — | — | Persist to SS_SaveManager |
| `OnFrameTimeSpike` | SpikeValue: Float | — | Handle sudden frame drop |
### Blueprint Flow
```
[BeginPlay]
└─► Initialize():
Detect platform (Console, PC Low/Med/High, Steam Deck, etc.)
Load saved settings from SS_SaveManager
If no saved settings:
GetRecommendedQuality() based on platform
Apply quality level
Start measurement timer with MeasurementInterval
[Timer - MeasurePerformance]
└─► MeasurePerformance():
Get frame time from GI_GameFramework.FrameTimeHistory
Add to local FrameTimeHistory, keep rolling window (last 60 frames)
Calculate AverageFrameTime (exponential moving average)
Calculate FrameTimeVariance (standard deviation of history)
If bAdaptiveMode and not bUserOverride:
EvaluateScaling()
[EvaluateScaling]
└─► If AverageFrameTime > TargetFrameTime * 1.2:
(Running slow, too many frames over target)
If TimeSince LastAdjustment > AdjustmentCooldown:
SetQualityLevel(CurrentQualityLevel - 1)
└─► If AverageFrameTime < TargetFrameTime * 0.7 and FrameTimeVariance < 2.0:
(Running fast, stable, can upgrade)
If TimeSince LastAdjustment > AdjustmentCooldown * 2:
SetQualityLevel(CurrentQualityLevel + 1)
└─► If FrameTimeVariance > 5.0:
(Unstable frame times, reduce quality for stability)
SetQualityLevel(CurrentQualityLevel - 1)
[SetQualityLevel]
└─► If CurrentQualityLevel == Level: return
└─► TargetQualityLevel = Level
└─► bIsScalingInProgress = true
└─► ApplyScalerSettings(Level)
└─► For each subsystem:
BPC_LightingManager: SetParticleLOD mapped to QualityLevel
BPC_AudioManager: Reduce active layers and spatial audio
BPC_VFXManager: SetParticleLOD mapped to QualityLevel
BPC_AtmosphereController: Reduce preset complexity
└─► Apply UE console variables for resolution, shadows, textures, foliage
└─► bIsScalingInProgress = false
└─► CurrentQualityLevel = TargetQualityLevel
└─► OnQualityLevelChanged.Broadcast(CurrentQualityLevel)
└─► SavePerformanceSettings()
[ApplyScalerSettings - Quality Levels]
└─► Low:
ResolutionScale = 0.7
ShadowQuality = 0 (off/low)
TexturePoolSize = 512 MB
PostProcessQuality = Low
ParticleLOD = 2 (minimum)
AudioQuality = 0 (mono, 2 layers max)
ViewDistance = Low
FoliageDensity = 0.3
AntiAliasing = TSR Low
GlobalIllumination = None
└─► Medium:
ResolutionScale = 0.85
ShadowQuality = 1 (medium)
TexturePoolSize = 1024 MB
PostProcessQuality = Medium
ParticleLOD = 1 (reduced)
AudioQuality = 1 (stereo, 4 layers)
ViewDistance = Medium
FoliageDensity = 0.6
AntiAliasing = TSR Medium
GlobalIllumination = Lumen Low
└─► High:
ResolutionScale = 1.0
ShadowQuality = 2 (high)
TexturePoolSize = 2048 MB
PostProcessQuality = High
ParticleLOD = 0 (full)
AudioQuality = 2 (surround, all layers)
ViewDistance = High
FoliageDensity = 1.0
AntiAliasing = TSR High
GlobalIllumination = Lumen High
└─► Ultra:
ResolutionScale = 1.0
ShadowQuality = 3 (ultra)
TexturePoolSize = 4096 MB
PostProcessQuality = Cinematic
ParticleLOD = 0 (full with extras)
AudioQuality = 3 (full spatial, max layers)
ViewDistance = Epic
FoliageDensity = 1.5
AntiAliasing = TSR Epic
GlobalIllumination = Lumen Ultra
└─► Cinematic:
ResolutionScale = 1.0 (or 1.25 for supersampling)
ShadowQuality = 3 (ultra with contact shadows)
TexturePoolSize = 8192 MB
PostProcessQuality = Cinematic with extra effects
ParticleLOD = 0 with extra presets
AudioQuality = 3
ViewDistance = Epic with forced high-resolution impostors
FoliageDensity = 2.0
AntiAliasing = TSR Epic with extra samples
GlobalIllumination = Lumen Ultra + Path Tracing toggle
[OnFrameTimeSpike]
└─► If spike > 50ms (sudden freeze):
SetQualityLevel(Low) temporarily
After 5 seconds of stable performance:
Gradually restore to previous level
```
### Event Dispatchers
| Name | Payload | Description |
|------|---------|-------------|
| `OnQualityLevelChanged` | NewLevel: EPerformanceQualityLevel | Broadcast quality change |
| `OnScalingTargetAdjusted` | Target: EScalingTarget, NewValue: Float | Per-target adjustment |
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`GI_GameFramework`](../01-core/04_GI_GameFramework.md) | Direct call | Frame time data source |
| [`SS_SaveManager`](../05-saveload/28_SS_SaveManager.md) | Direct call | Save/load settings |
| [`BPC_LightingManager`](65_BPC_LightingManager.md) | Get from player | Light quality reduction |
| [`BPC_AudioManager`](66_BPC_AudioManager.md) | Get from player | Audio quality reduction |
| [`BPC_VFXManager`](67_BPC_VFXManager.md) | Get from player | Particle LOD control |
| [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) | Get from player | Preset complexity reduction |
| [`WBP_SettingsUI`](../06-ui/44_WBP_SettingsUI.md) | Event | User quality override |
| [`BPC_PlayerController`] | Cast | Console command execution |
### Reuse Notes
- Works with any subsystem that exposes quality API: Lighting, Audio, VFX, etc.
- Frame time history rolling window: last 60 frames for smooth average
- Adaptive mode is off by default; user must enable in settings
- User override locks quality level until adaptive mode is re-enabled
- PlatformProfile stores detected hardware for initial recommendation
- Adjustment cooldown prevents oscillation between quality levels
- Per-scaling-target notifications enable UI to show specific quality changes

View File

@@ -0,0 +1,288 @@
# 70 — BPC_ProceduralEncounter
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) — Encounter difficulty scaling
- [`BPC_FearSystem`](90_BPC_FearSystem.md) — Fear-based encounter intensity
- [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) — Atmosphere-aligned encounters
- [`BP_DynamicEvent`](68_BP_DynamicEvent.md) — Encounter as dynamic event
- [`DA_EncounterTable`](../12-content/76_DA_EncounterTable.md) — Encounter data tables (Phase 12)
- [`I_Damageable`](../08-weapons/58_I_Damageable.md) — Enemy damage integration
- [`AI_EnemyBase`](../09-ai/58_AI_EnemyBase.md) — Enemy AI controller
- [`BP_EnemyBase`](../09-ai/57_BP_EnemyBase.md) — Enemy pawn reference
### Purpose
Procedurally generates enemy encounters, ambushes, and spawn events based on player state, difficulty, fear level, atmosphere, and narrative progression. Operates as a tactical encounter director that decides when, where, and what enemies to spawn, how they behave, and when to retreat or reinforce. Prevents encounter fatigue by managing cooldowns, variety, and pacing. Supports wave-based encounters, ambushes, patrol reinforcement, and boss encounters.
### Enums
**EEncounterType**
| Value | Description |
|-------|-------------|
| Patrol | Wandering enemy, player can evade |
| Ambush | Pre-placed enemies triggered by player |
| WaveBased | Multiple waves of increasing difficulty |
| Reinforcement | Enemies called in during combat |
| Boss | Single powerful enemy |
| Escalation | Increasing intensity until player leaves area |
| Environmental | Hazard-based encounter (not enemies) |
| Scripted | Level designer placed encounter |
**ESpawnMethod**
| Value | Description |
|-------|-------------|
| FromLocations | Use pre-placed spawn points |
| FromNavMesh | Random point on nav mesh |
| FromVolumes | Spawn in volume bounds |
| FromPortals | Enemy emerges from gate/door |
| FromVent | Spawn at vent locations |
| CeilingDrop | Enemy drops from above |
| ClosestNav | Closest valid nav point to player |
**EEncounterIntensity**
| Value | Description |
|-------|-------------|
| Low | 12 weak enemies |
| Moderate | 23 standard enemies |
| High | 35 mixed enemies |
| Critical | 5+ enemies with elites |
| Boss | Single boss enemy |
### Structs
**FEncounterSpawnSlot**
| Field | Type | Description |
|-------|------|-------------|
| SpawnLocation | FVector | World position |
| EnemyClass | TSubclassOf\<BP_EnemyBase\> | Enemy to spawn |
| SpawnDelay | Float | Delay before this enemy appears |
| bDropIn | Bool | Animated drop-in |
| SpawnEffect | UNiagaraSystem | Visual spawn effect |
| PatrolRouteIndex | Int32 | Patrol path to follow |
**FEncounterWave**
| Field | Type | Description |
|-------|------|-------------|
| WaveName | FName | Identifier |
| SpawnSlots | TArray\<FEncounterSpawnSlot\> | Enemies in this wave |
| ActivationDelay | Float | Time before wave starts |
| bWaitForAllDead | Bool | Next wave waits for all dead |
| DifficultyMultiplier | Float | Wave-specific difficulty |
| DialogueTrigger | FName | Optional dialogue on wave start |
### Variables
| Name | Type | Description |
|------|------|-------------|
| `ActiveEncounterType` | EEncounterType | Current encounter type |
| `EncounterState` | FName | Active, Cooldown, Completed |
| `bPlayerInEncounterZone` | Bool | Player proximity trigger |
| `EncounterRadius` | Float | Zone size |
| `MinEnemyDistance` | Float | Min distance from player to spawn |
| `MaxConcurrentEnemies` | Int32 | Hard limit on active enemies |
| `ActiveEnemies` | TArray\<BP_EnemyBase\> | Currently alive enemies |
| `Waves` | TArray\<FEncounterWave\> | Defined waves |
| `CurrentWaveIndex` | Int32 | Active wave |
| `bWaveInProgress` | Bool | Currently executing wave |
| `CooldownRemaining` | Float | Encounter cooldown |
| `MinCooldown` | Float | Min time between encounters |
| `MaxCooldown` | Float | Max time between encounters |
| `EncounterCount` | Int32 | Encounters this session |
| `MaxEncountersPerZone` | Int32 | Encounters before exhaustion |
| `bZoneExhausted` | Bool | No more encounters in this zone |
| `SpawnLocations` | TArray\<FEncounterSpawnSlot\> | Configured spawn points |
| `bUseNavMesh` | Bool | Use nav mesh for spawns |
| `EncounterVolumeBox` | FBox | Spawn volume bounds |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Register spawn points, validate setup |
| `TriggerEncounter` | Type: EEncounterType, Intensity: EEncounterIntensity | — | Start encounter |
| `TriggerWaveBasedEncounter` | Intensity: EEncounterIntensity | — | Multi-wave encounter |
| `SpawnWave` | WaveIndex: Int32 | — | Execute wave spawning |
| `SpawnSingleEnemy` | Slot: FEncounterSpawnSlot | BP_EnemyBase | Spawn one enemy |
| `GetRandomSpawnLocation` | — | FVector | Pick valid spawn point |
| `GetSpawnLocationByMethod` | Method: ESpawnMethod | FVector | Contextual spawn position |
| `OnEnemyKilled` | Enemy: BP_EnemyBase | — | Track enemy death |
| `OnAllEnemiesDead` | — | — | Wave completed or encounter ended |
| `ReinforceEncounter` | Count: Int32 | — | Spawn additional enemies |
| `EndEncounter` | — | — | Cleanup and cooldown |
| `RetreatEnemies` | — | — | All enemies flee |
| `CalculateIntensity` | — | EEncounterIntensity | Based on difficulty + fear + atmosphere |
| `IsSpawnLocationValid` | Location: FVector | Bool | Check line of sight, nav mesh |
| `IsInEncounterZone` | PlayerLocation: FVector | Bool | Proximity check |
| `StartCooldown` | — | — | Begin cooldown timer |
| `ExhaustZone` | — | — | Mark zone as depleted |
| `ResetEncounter` | — | — | Full reset for reuse |
### Blueprint Flow
```
[Initialize]
└─► Register spawn point locations from child actors or volume
└─► If bUseNavMesh:
Get nav mesh bounds from EncounterVolumeBox
└─► Listen for player proximity via overlap events
└─► Set initial EncounterState = Ready
[TriggerEncounter]
└─► If CooldownRemaining > 0 or bZoneExhausted: return
└─► EncounterState = Active
└─► ActiveEncounterType = Type
└─► CalculateIntensity()
└─► Based on Type:
Patrol: Spawn 1-2 patrol enemies, set patrol routes
Ambush: All enemies spawn at once, no delay, trigger dialogue
WaveBased: TriggerWaveBasedEncounter(Intensity)
Reinforcement: Spawn 1-3 enemies near existing combat
Boss: Spawn single boss enemy, lock area
Escalation: Start wave timer, spawn escalating until retreat or all dead
Environmental: Trigger hazard events instead of enemies
Scripted: Execute designer-defined encounter
└─► Broadcast OnEncounterStarted
[TriggerWaveBasedEncounter]
└─► CurrentWaveIndex = 0
└─► For each wave in Waves:
Wave.DifficultyMultiplier = Get difficulty scale from BPC_DifficultyManager
Apply difficulty scaling to each enemy class
└─► SpawnWave(0)
[SpawnWave]
└─► bWaveInProgress = true
└─► For each FEncounterSpawnSlot in Waves[WaveIndex].SpawnSlots:
Delay(SpawnSlot.SpawnDelay)
SSpawnSingleEnemy(SpawnSlot)
└─► If Waves[WaveIndex].DialogueTrigger:
Trigger dialogue via BPC_DialoguePlayback
[SpawnSingleEnemy]
└─► SpawnLocation = Slot.SpawnLocation
└─► If Slot.SpawnLocation == ZeroVector:
SpawnLocation = GetSpawnLocationByMethod(FromLocations or FromNavMesh)
└─► If not IsSpawnLocationValid(SpawnLocation): return
└─► Enemy = SpawnActor Deferred(Slot.EnemyClass, SpawnLocation)
└─► Set difficulty-scaled stats on Enemy:
Health *= DifficultyManager.HealthMultiplier
Damage *= DifficultyManager.DamageMultiplier
Speed *= DifficultyManager.SpeedMultiplier
└─► If Slot.PatrolRouteIndex >= 0:
Assign patrol path to enemy
└─► FinishSpawning(Enemy)
└─► If Slot.bDropIn:
Play drop-in animation on Enemy
└─► If Slot.SpawnEffect:
UNiagaraFunctionLibrary::SpawnSystemAtLocation
└─► Bind OnEnemyKilled to Enemy.OnDestroyed
└─► ActiveEnemies.Add(Enemy)
[OnEnemyKilled]
└─► ActiveEnemies.Remove(Enemy)
└─► Add to MetricsTracker for scoring
└─► If ActiveEnemies.Num() == 0:
OnAllEnemiesDead()
└─► If EncounterState == Active and ActiveEncounterType == Escalation:
CalculateIntensity()
If should escalate:
SpawnWave more enemies
If should de-escalate:
RetreatEnemies()
[OnAllEnemiesDead]
└─► If ActiveEncounterType == WaveBased:
CurrentWaveIndex++
If CurrentWaveIndex < Waves.Num():
SpawnWave(CurrentWaveIndex)
Else:
EndEncounter()
└─► Else:
EndEncounter()
[EndEncounter]
└─► EncounterState = Completed
└─► EncounterCount++
└─► If EncounterCount >= MaxEncountersPerZone:
ExhaustZone()
└─► Else:
StartCooldown(MinCooldown, MaxCooldown)
└─► Broadcast OnEncounterEnded
└─► Reward player via BPC_PlayerMetricsTracker
[CalculateIntensity]
└─► Base intensity from BPC_DifficultyManager.CurrentDifficulty
└─► Multiply by BPC_FearSystem.CurrentFearLevel factor (0.5 at Calm, 2.0 at Panic)
└─► Adjust by BPC_AtmosphereController.CurrentState:
Exploration: *0.5 (fewer enemies)
Suspense: *1.0 (standard)
Combat: *2.0 (more enemies)
Panic: *1.5 (intense but fewer than combat)
└─► Clamp to EEncounterIntensity range
└─► Return
[GetSpawnLocationByMethod]
└─► FromLocations: Pick random from SpawnLocations array
└─► FromNavMesh: Query UNavigationSystemV1::GetRandomPointInNavigableRadius
Ensure distance from player >= MinEnemyDistance
└─► FromVolumes: Random point within EncounterVolumeBox
Project to nav mesh
└─► FromPortals: Pre-placed portal actor locations
└─► FromVent: Vent location list
└─► CeilingDrop: Above player location + random offset
Ensure valid nav mesh below
[IsSpawnLocationValid]
└─► Check distance from player >= MinEnemyDistance
└─► Line trace from player to spawn location:
If blocked and distance < 500: location is behind obstacle, valid (ambush)
If clear and distance < 500: location is visible, valid for patrol
If distance > 2000: always valid
└─► Check nav mesh projection is valid
└─► Check not overlapping existing enemies
```
### Event Dispatchers
| Name | Payload | Description |
|------|---------|-------------|
| `OnEncounterStarted` | EncounterType: EEncounterType, Intensity: EEncounterIntensity | Encounter began |
| `OnEncounterEnded` | bPlayerSurvived: Bool, EnemiesKilled: Int32 | Encounter finished |
| `OnWaveStarted` | WaveIndex: Int32, WaveName: FName | New wave beginning |
| `OnWaveCompleted` | WaveIndex: Int32 | Wave cleared |
| `OnEnemySpawned` | Enemy: BP_EnemyBase | Individual enemy spawned |
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) | Direct call | Difficulty scaling for encounter |
| [`BPC_FearSystem`](90_BPC_FearSystem.md) | Get owner | Fear influence on intensity |
| [`BPC_AtmosphereController`](64_BPC_AtmosphereController.md) | Get owner | Atmosphere alignment |
| [`BP_DynamicEvent`](68_BP_DynamicEvent.md) | Cast | Encounter as dynamic event |
| [`AI_EnemyBase`](../09-ai/55_AI_EnemyBase.md) | On spawned | Set encounter-aware behaviors |
| [`BP_EnemyBase`](../09-ai/57_BP_EnemyBase.md) | On spawned | Set difficulty-scaled stats |
| [`BPC_PlayerMetricsTracker`](../02-player/14_BPC_PlayerMetricsTracker.md) | Event | Encounter scoring |
| [`BPC_NarrativeState`](../07-narrative/36_BPC_NarrativeState.md) | Event | Narrative-gated encounters |
| [`BPC_DialoguePlayback`](../07-narrative/37_BPC_DialoguePlayback.md) | Event | Wave-start dialogue |
### Reuse Notes
- Place BPC_ProceduralEncounter on a volume actor or game mode
- Configure spawn points as child actors or arrays in details panel
- Encounter exhaustion prevents infinite grinding in one area
- Difficulty + Fear + Atmosphere combine to produce dynamic intensity
- Nav mesh spawning ensures enemies always have valid pathfinding
- Encounter state machine: Ready -> Active -> Cooldown -> Exhausted
- All encounters work with or without pre-placed spawn locations
- Each enemy spawned gets difficulty-scaled stats at spawn time

View File

@@ -0,0 +1,194 @@
# BPC_AdaptiveEnvironmentDirector — Adaptive Environment Director
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- **Requires:** [`BPC_PlaystyleClassifier`](BPC_PlaystyleClassifier.md) — Subscribes to `OnPlaystyleChanged` for routing
- **Required By:** [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) — Receives atmosphere change commands
- **Required By:** [`BPC_ScareEventSystem`](BPC_ScareEventSystem.md) — Coordinates scare scheduling with playstyle
- **Required By:** [`BPC_PacingDirector`](BPC_PacingDirector.md) — Receives pacing guidance
- **Required By:** [`BPC_MemoryDriftSystem`](BPC_MemoryDriftSystem.md) — Triggers room mutations based on adaptation rules
- **Required By:** [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) — Music state changes (via `SetMusicLayer()`)
- **Engine/Plugin Requirements:** GameplayTags, DA_AdaptationRule data assets
### Purpose
The master controller of all adaptive systems. Receives playstyle classification and orchestrates world responses — atmosphere changes, scare scheduling, pacing adjustments, and memory drift triggers. Enforces cooldowns between major adaptive changes to prevent overwhelming the player.
---
## 1. Enums
*No new enums. Uses playstyle tags and atmosphere tags from dependent systems.*
---
## 2. Structs
*No new structs defined. Uses [`DA_AdaptationRule`](../14-data-assets/DA_AdaptationRule.md) for rule definitions.*
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `AdaptationRules` | Array of DA_AdaptationRule | Empty | Adaptation | Rule set defining playstyle → adaptation mappings |
| `AdaptationCooldown` | Float | 60.0 | Adaptation | Minimum seconds between major adaptive changes |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `LastAdaptationTime` | Float | 0.0 | Internal | Game time of last adaptation trigger |
| `ActiveAtmosphereTag` | GameplayTag | None | Internal | Currently active atmosphere state |
| `CooldownTimer` | TimerHandle | — | Internal | Timer tracking cooldown expiry |
---
## 4. Functions
### Public Functions
#### `EvaluateAndAdapt` → `void`
- **Description:** Main entry point. Called when playstyle changes or on timer. Evaluates adaptation rules against current playstyle and triggers appropriate responses.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `PlaystyleTag` | GameplayTag | Current player playstyle |
- **Blueprint Authority:** Any
- **Flow:** Check cooldown → iterate AdaptationRules → find matching rules → execute adaptation actions → update LastAdaptationTime
#### `RequestAdaptation` → `void`
- **Description:** Forces an immediate adaptation check bypassing cooldown. Used for scripted/boss encounters.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `AdaptationTag` | GameplayTag | Specific adaptation to trigger |
- **Blueprint Authority:** Any
#### `SetAdaptationRules` → `void`
- **Description:** Replaces the active rule set (used when entering new chapters/zones).
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `NewRules` | Array of DA_AdaptationRule | Replacement rule set |
- **Blueprint Authority:** Any
#### `GetActiveAtmosphere` → `GameplayTag`
- **Description:** Returns the currently active atmosphere tag.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `DisableAdaptation` → `void`
- **Description:** Disables all adaptive responses. Used during cutscenes, boss fights, or scripted sequences.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `bDisabled` | Bool | True = disable, False = re-enable |
- **Blueprint Authority:** Any
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnAdaptationTriggered` | RuleTag: GameplayTag, PlaystyleTag: GameplayTag | Public | An adaptation rule was executed |
| `OnAtmosphereChangeRequested` | NewAtmosphereTag: GameplayTag | Public | Atmosphere change needed |
| `OnScareScheduleRequested` | ScareTier: E_ScareTier | Public | Scare event should be considered |
| `OnMemoryDriftRequested` | DriftIntensity: Float | Public | Memory drift/room mutation suggested |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay`
- **Description:** Subscribes to [`BPC_PlaystyleClassifier`](BPC_PlaystyleClassifier.md)`OnPlaystyleChanged` dispatcher.
- **Flow:**
1. Find BPC_PlaystyleClassifier and subscribe to OnPlaystyleChanged
2. If classifier exists and has a current playstyle, perform initial EvaluateAndAdapt
### Event: `OnPlaystyleChanged`
- **Description:** Called when playstyle classification changes. Triggers re-evaluation.
- **Flow:**
1. If cooldown has elapsed → EvaluateAndAdapt(NewPlaystyleTag)
2. Else → queue adaptation for after cooldown expiry
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[BeginPlay] --> B[Subscribe to PlaystyleClassifier.OnPlaystyleChanged]
B --> C[Idle]
C --> D{OnPlaystyleChanged fires?}
D --> E{AdaptationCooldown elapsed?}
E -->|Yes| F[EvaluateAndAdapt NewPlaystyleTag]
E -->|No| G[Queue for cooldown expiry]
F --> H[Iterate AdaptationRules]
H --> I{Rule matches playstyle?}
I -->|Yes| J[Execute adaptation actions]
I -->|No| K[Skip rule]
J --> L[Route to sub-systems]
L --> M[Atmosphere → BPC_AtmosphereStateController]
L --> N[Scare → BPC_ScareEventSystem]
L --> O[Pacing → BPC_PacingDirector]
L --> P[Audio → SS_AudioManager.SetMusicLayer]
L --> Q[Drift → BPC_MemoryDriftSystem]
M --> R[Update LastAdaptationTime]
N --> R
O --> R
P --> R
Q --> R
R --> S[Broadcast OnAdaptationTriggered]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| [`BPC_PlaystyleClassifier`](BPC_PlaystyleClassifier.md) | Dispatcher (`OnPlaystyleChanged`) | `NewPlaystyleTag: GameplayTag` |
| [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) | Direct call | `SetAtmosphere(NewAtmosphereTag, BlendTime)` |
| [`BPC_ScareEventSystem`](BPC_ScareEventSystem.md) | Direct call | Scare scheduling parameters based on playstyle |
| [`BPC_PacingDirector`](BPC_PacingDirector.md) | Direct call | Tension adjustments |
| [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) | Direct call | `SetMusicLayer(LayerIndex, Sound, Intensity)` |
| [`BPC_MemoryDriftSystem`](BPC_MemoryDriftSystem.md) | Direct call | `TriggerDrift(Intensity)` |
| [`DA_AdaptationRule`](../14-data-assets/DA_AdaptationRule.md) | Data asset read | Rule definition: conditions, targets, parameters |
---
## 9. Validation / Testing Checklist
- [ ] AdaptationRules array is populated for the current level/chapter
- [ ] EvaluateAndAdapt respects AdaptationCooldown
- [ ] OnPlaystyleChanged triggers correct rule for the new playstyle
- [ ] RequestAdaptation bypasses cooldown for scripted events
- [ ] SetAdaptationRules swaps rule set cleanly on chapter transitions
- [ ] DisableAdaptation prevents all adaptive responses during cutscenes
- [ ] Edge case: no rules match current playstyle → no changes made
- [ ] Edge case: multiple rules match → all executed in order
- [ ] Edge case: adaptation cooldown during rapid playstyle changes → only last change applies
---
## 10. Reuse Notes
- AdaptationRules are defined per chapter/zone via DA_AdaptationRule data assets — swap rules on level load
- AdaptationCooldown prevents jarring rapid changes; tune per project for desired responsiveness
- This director is the single routing hub for all adaptive sub-systems — do NOT create direct cross-system links
- For non-adaptive games, leave AdaptationRules empty — all sub-systems run on their defaults
- Extend DA_AdaptationRule with new action types for project-specific adaptations
---
*Specification based on Master Section 9.2, line 2737.*

View File

@@ -0,0 +1,165 @@
# BPC_AtmosphereStateController — Atmosphere State Controller
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`BPC_FearSystem`](90_BPC_FearSystem.md) — Fear state input
- [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) — Tuning bias
- [`BPC_LightEventController`](BPC_LightEventController.md) — Lighting response orchestration
- [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) — Audio ambience orchestration (via `PlayAmbient()`, `SetMusicLayer()`)
- [`BPC_NarrativeStateSystem`](../07-narrative/58_BPC_NarrativeStateSystem.md) — Story phase input
- [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player state input
- [`BPC_PerformanceScaler`](91_BPC_PerformanceScaler.md) — Performance budget input
### Purpose
Central atmosphere orchestrator. Manages the overall mood and tension of the environment by coordinating lighting, audio, VFX, and other atmospheric systems based on the current fear state, narrative phase, difficulty bias, and player context. Acts as the single decision point for all ambient presentation, decoupling individual media systems from game logic.
### Variables
| Name | Type | Description |
|------|------|-------------|
| `CurrentAtmosphereState` | EAtmosphereState | Active atmosphere preset |
| `AtmosphereIntensity` | Float (0.01.0) | Global intensity bias |
| `FearIntensityBias` | Float | From DifficultyManager |
| `NarrativeAtmosphereOverride` | EAtmosphereState | Story-forced atmosphere |
| `bAllowAtmosphereChanges` | Bool | Master toggle |
| `bHasNarrativeOverride` | Bool | Active story override |
| `AmbientTransitionTime` | Float | Seconds for crossfade between states |
| `CurrentLightingPreset` | FName | Active lighting key |
| `CurrentAudioPreset` | FName | Active ambient audio key |
| `CurrentVFXPreset` | FName | Active VFX preset key |
| `AtmosphereBlendWeight` | Float (0.01.0) | Current crossfade progress |
| `bIsInPanicMode` | Bool | Panic state active |
| `PanicModePreset` | FName | Preset for panic state |
| `SafeZonePreset` | FName | Preset for safe zones |
| `ExplorationPreset` | FName | Default exploration preset |
| `CombatPreset` | FName | Combat tension preset |
| `InvestigationPreset` | FName | Suspense/investigation preset |
| `AtmospherePresets` | TMap<FName, FAtmospherePreset> | All registered presets |
### Enums
| Enum | Values | Description |
|------|--------|-------------|
| `EAtmosphereState` | Exploration, Suspense, Combat, Panic, SafeZone, Cinematic, Custom | Atmosphere modes |
### Structs
| Struct | Fields | Description |
|--------|--------|-------------|
| `FAtmospherePreset` | State: EAtmosphereState, LightingKey: FName, AudioAmbienceKey: FName, VFXPresetKey: FName, Intensity: Float, TransitionTime: Float, bLoops: Bool | Complete atmosphere configuration |
| `FAtmosphereTransition` | FromState: EAtmosphereState, ToState: EAtmosphereState, Duration: Float, BlendCurve: UCurveFloat | Transition definition |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Register presets, bind events |
| `SetAtmosphere` | State: EAtmosphereState | — | Transition to atmosphere state |
| `SetAtmosphereIntensity` | Intensity: Float | — | Override global intensity |
| `GetCurrentAtmosphere` | — | EAtmosphereState | Current state |
| `RegisterPreset` | Key: FName, Preset: FAtmospherePreset | — | Add custom preset |
| `ApplyNarrativeOverride` | State: EAtmosphereState | — | Story-forced atmosphere |
| `ClearNarrativeOverride` | — | — | Return to gameplay atmosphere |
| `TriggerPanicMode` | — | — | Immediate panic atmosphere |
| `EndPanicMode` | — | — | Restore previous atmosphere |
| `SetTuningBias` | Bias: Float | — | From DifficultyManager |
| `GetPresetForState` | State: EAtmosphereState | FAtmospherePreset | Lookup preset |
| `EvaluateAtmosphere` | — | — | Determine best state from inputs |
| `ApplyPreset` | Preset: FAtmospherePreset | — | Push to all subsystems |
| `BlendToAtmosphere` | TargetState: EAtmosphereState, Duration: Float | — | Smooth transition |
### Event Dispatchers
| Name | Parameters | Fired When |
|------|-----------|-----------|
| `OnAtmosphereChanged` | NewState: EAtmosphereState, Intensity: Float | Atmosphere state changes |
| `OnAtmosphereIntensityChanged` | NewIntensity: Float | Intensity bias changes |
| `OnPanicAtmosphereStarted` | — | Panic mode activated |
| `OnPanicAtmosphereEnded` | — | Panic mode deactivated |
| `OnNarrativeOverrideApplied` | State: EAtmosphereState | Story override |
### Blueprint Flow
```
[Tick]
└─► If bAllowAtmosphereChanges AND not bHasNarrativeOverride:
EvaluateAtmosphere()
└─► If atmosphere state should change:
BlendToAtmosphere(TargetState, AmbientTransitionTime)
[EvaluateAtmosphere — main decision logic]
└─► Inputs: Current fear level, player metrics, narrative phase
└─► Priority order:
1. If bIsInPanicMode: Return Panic preset
2. If narrative override active: Return narrative preset
3. If player in combat with fear > Terrified: Return Combat preset
4. If player in combat: Return Combat preset
5. If player investigating or suspicious: Return Suspense preset
6. If player in safe zone: Return SafeZone preset
7. If player exploring: Return Exploration preset
└─► Apply FearIntensityBias to preset intensity
└─► Return selected preset
[SetAtmosphere — direct override]
└─► Lookup or get preset for state
└─► ApplyPreset(Preset)
└─► CurrentAtmosphereState = State
└─► Broadcast OnAtmosphereChanged
[ApplyPreset — push to subsystems]
└─► BPC_LightEventController.SetLightingPreset(Preset.LightingKey, TransitionTime)
└─► SS_AudioManager.PlayAmbient(Preset.AudioAmbienceKey, TransitionTime)
└─► Apply preset intensity modifiers to each subsystem
└─► BlendWeight = 0.0 → lerp to 1.0 over TransitionTime
[TriggerPanicMode]
└─► bIsInPanicMode = true
└─► ApplyPreset(AtmospherePresets[PanicModePreset])
└─► Broadcast OnPanicAtmosphereStarted
[EndPanicMode]
└─► bIsInPanicMode = false
└─► EvaluateAtmosphere()
└─► Broadcast OnPanicAtmosphereEnded
[ApplyNarrativeOverride]
└─► bHasNarrativeOverride = true
└─► NarrativeAtmosphereOverride = State
└─► SetAtmosphere(State)
└─► Broadcast OnNarrativeOverrideApplied
[ClearNarrativeOverride]
└─► bHasNarrativeOverride = false
└─► EvaluateAtmosphere()
```
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_FearSystem`](90_BPC_FearSystem.md) | Get Owner Component | Fear state for atmosphere selection |
| [`BPC_DifficultyManager`](89_BPC_DifficultyManager.md) | Get Owner Component | Intensity bias, fear modifier |
| [`BPC_LightEventController`](BPC_LightEventController.md) | Direct call | Set lighting preset |
| [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) | Direct call | Set ambient audio, music layers |
| [`BPC_NarrativeStateSystem`](../07-narrative/58_BPC_NarrativeStateSystem.md) | Get Owner Component | Narrative phase for override |
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Get Owner Component | Player context metrics |
| [`BPC_PerformanceScaler`](91_BPC_PerformanceScaler.md) | Direct call | Scale quality based on performance |
| [`SS_UIManager`](../06-ui/44_SS_UIManager.md) | Subsystem | UI atmosphere indicators |
| [`BP_NarrativeTriggerVolume`](../07-narrative/BP_NarrativeTriggerVolume.md) | Overlap event | Atmosphere cue from volume |
### Reuse Notes
- Single instance on player character or persistent game actor
- All atmosphere presets are data-driven (can be expanded via Data Assets)
- Presets are keyed by FName for extensibility without enum changes
- Narrative overrides take priority over gameplay atmosphere
- Panic mode is a temporary atmosphere state; EndPanicMode restores evaluation
- Subsystems (Lighting, Audio, VFX) are decoupled; AtmosphereStateController only sets keys and intensities
- Blend curves control crossfade smoothness between states
- Renamed from `BPC_AtmosphereController` to `BPC_AtmosphereStateController` per Master naming convention.
- Cross-references updated: `BPC_LightingManager``BPC_LightEventController`, `BPC_AudioManager``BPC_AudioAtmosphereController``SS_AudioManager` (132 — deprecated), `BPC_VFXManager` removed (not in Master).

View File

@@ -0,0 +1,165 @@
# BPC_AudioAtmosphereController — Audio Atmosphere Controller
> **⚠️ DEPRECATED — Phase 14d**
> This component is superseded by [`SS_AudioManager` (132)](../10-adaptive/132_SS_AudioManager.md), the single entry point for all MetaSounds audio playback.
> - `PlayOneShotSFX()` → `SS_AudioManager.PlaySFX()`
> - `PlayAmbientSound()` → `SS_AudioManager.PlayAmbient()`
> - `SetMusicLayer()` → `SS_AudioManager.SetMusicLayer()`
> - `PlayDialogue()` → `SS_AudioManager.PlayDialogue()`
> - `SetReverb()` → `SS_AudioManager.SetRoomPreset()` via `BP_RoomAudioZone`
> - `FearReactiveAudioUpdate()` → `SS_AudioManager.UpdateFearAudio()`
> - Volume modifiers → `SS_AudioManager.SetBusVolume()` bound to `SS_SettingsSystem`
> See [`metasounds-audio-system.md`](../../architecture/metasounds-audio-system.md) for full migration guide.
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) — Atmosphere state receiver
- [`BPC_FearSystem`](90_BPC_FearSystem.md) — Fear-driven audio modulation
- [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) — Player state context
- [`UAudioComponent`] — UE built-in audio component
- [`UMetaSoundSource`] — UE 5.5 MetaSounds
### Purpose
Centralized audio controller that manages ambient soundscapes, fear-reactive audio modulation, dynamic music layers, spatial audio events, and dialogue playback. Receives atmosphere state changes from AtmosphereStateController and adjusts audio parameters accordingly. Supports MetaSounds for dynamic audio generation (UE 5.5+), real-time audio parameter modulation, and multi-layered audio blending.
### Variables
| Name | Type | Description |
|------|------|-------------|
| `ActiveAudioPreset` | FName | Current audio snapshot |
| `TargetAudioPreset` | FName | Audio preset being transitioned to |
| `MasterVolumeModifier` | Float (0.02.0) | Global volume multiplier |
| `SFXVolumeModifier` | Float (0.02.0) | Sound effects volume |
| `MusicVolumeModifier` | Float (0.02.0) | Music layer volume |
| `AmbientVolumeModifier` | Float (0.02.0) | Ambient layer volume |
| `DialogueVolumeModifier` | Float (0.02.0) | Dialogue volume |
| `FearAudioIntensity` | Float (0.01.0) | Fear-based audio effect amount |
| `CurrentAudioLayers` | TArray<FAudioLayerState> | Active audio layers |
| `AmbientAudioComp` | UAudioComponent | Main ambient sound |
| `MusicAudioComp` | UAudioComponent | Music layer |
| `SubMusicComp1` | UAudioComponent | Sub-music layer 1 |
| `SubMusicComp2` | UAudioComponent | Sub-music layer 2 |
| `ReverbEffect` | UReverbEffect | Current reverb setting |
| `OcclusionIntensity` | Float | Obstacle-based audio occlusion |
| `ReverbWetLevel` | Float | Reverb mix amount |
| `bUseMetaSounds` | Bool | Enable MetaSound support |
| `ActiveMetaSound` | UMetaSoundSource | Current active MetaSound |
| `AudioParameterMap` | TMap<FName, float> | Dynamic MetaSound parameters |
### Structs
**FAudioLayerState**
| Field | Type | Description |
|-------|------|-------------|
| LayerName | FName | Identifier |
| AudioComp | UAudioComponent | Assigned component |
| TargetVolume | Float | Desired volume |
| CurrentVolume | Float | Actual volume (lerped) |
| FadeSpeed | Float | Volume fade rate |
| bIsPlaying | Bool | Active state |
| Priority | Int32 | Layer priority (higher = more important) |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Cache references, create audio components |
| `SetAudioPreset` | PresetKey: FName, TransitionTime: Float | — | Queue audio preset transition |
| `ApplyPresetDirect` | PresetKey: FName | — | Instant preset application |
| `PlayAmbientSound` | Sound: USoundBase, FadeInTime: Float | — | Crossfade ambient |
| `StopAmbientSound` | FadeOutTime: Float | — | Fade out ambient |
| `SetMusicLayer` | LayerIndex: Int32, Sound: USoundBase, TargetVolume: Float, FadeTime: Float | — | Set music layer with crossfade |
| `StopAllMusic` | FadeOutTime: Float | — | Fade out all music layers |
| `PlayOneShotSFX` | Sound: USoundBase, Location: FVector | — | Spatial sound effect |
| `PlayDialogue` | DialogueSound: USoundBase | — | Dialogue playback with prioritization |
| `StopDialogue` | FadeOutTime: Float | — | Stop current dialogue |
| `SetReverb` | Reverb: UReverbEffect, WetLevel: Float, TransitionTime: Float | — | Apply reverb effect |
| `SetOcclusion` | Intensity: Float | — | Obstacle audio occlusion |
| `SetAudioParameter` | ParameterName: FName, Value: Float | — | MetaSound parameter update |
| `FearReactiveAudioUpdate` | FearLevel: Float, Threshold: EFearThreshold | — | Per-tick fear audio modulation |
| `OnAtmosphereChanged` | NewState: EAtmosphereState | — | React to atmosphere change |
| `HandleRoomChange` | RoomAcousticPreset: FName | — | Room-based reverb switching |
### Blueprint Flow
```
[Tick]
└─► For each FAudioLayerState:
Lerp CurrentVolume toward TargetVolume at FadeSpeed
Update AudioComp volume
└─► FearReactiveAudioUpdate(CurrentFearLevel, CurrentThreshold)
[SetAudioPreset]
└─► If TransitionTime > 0:
Begin crossfade: lerp volumes from current to target
Transition layers sequentially by priority
└─► Else:
ApplyPresetDirect(PresetKey)
[FearReactiveAudioUpdate]
└─► Based on fear threshold:
Calm: Normal audio, no modulation
Uneasy: SubMusicLayer1 fade in (low drone), slight reverb increase
Nervous: Music pitch shift down, ambient filter low-pass, increased reverb
Afraid: Heartbeat layer, audio distortion, occlusion simulation, surround pan random
Terrified: All audio layers active, dissonant harmony, heavy reverb + echo, random cuts
Panic: Audio stutter effect, high-frequency ringing, audio buffer glitch simulation
└─► Apply fear modulation:
Set MasterVolumeModifier based on fear: 1.0 at 0 fear, 1.2 at high fear
Set SFX distortion/overtone shift
Adjust reverb wet level proportional to fear
[OnAtmosphereChanged]
└─► Exploration: Normal ambient, no music, natural reverb
└─► Suspense: Low drone music, increased reverb, distant SFX
└─► Combat: High-energy music, aggressive SFX, reduced reverb
└─► Panic: Chaotic layers, distortion, stutter
└─► SafeZone: Calm music, no reverb, warm ambient
└─► Cinematic: Dialogue-priority mode, cinematic mix, wide soundstage
[HandleRoomChange]
└─► Lookup room acoustic preset from data asset
└─► Apply reverb, occlusion, filter settings
└─► Fade transition over 1-2 seconds
[PlayDialogue]
└─► If current dialogue priority >= incoming dialogue priority:
Queue dialogue
└─► Else:
Stop current, play new
└─► Duck (lower volume) all other audio layers during dialogue
└─► Restore on dialogue complete
[PlayOneShotSFX]
└─► Use UGameplayStatics::SpawnSoundAtLocation
└─► Apply occlusion based on line-of-sight to player
└─► Apply distance-based low-pass filter
```
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) | Called by | Receive atmosphere state changes |
| [`BPC_FearSystem`](90_BPC_FearSystem.md) | Get Owner Component | Fear level for reactive audio |
| [`BPC_PlayerMetricsTracker`](../02-player/15_BPC_PlayerMetricsTracker.md) | Direct call | Contextual audio (health low, hiding) |
| [`BPC_PerformanceScaler`](91_BPC_PerformanceScaler.md) | Direct call | Reduce audio quality on low spec |
| [`BP_Checkpoint`](../05-saveload/BP_Checkpoint.md) | Event | Restore audio state on load |
| [`BPC_DialoguePlaybackSystem`](../07-narrative/40_BPC_DialoguePlaybackSystem.md) | Direct call | Dialogue audio routing |
### Reuse Notes
- Audio layers allow stacking multiple ambiences
- MetaSound parameter map enables dynamic audio without new Blueprint logic
- Dialogue ducking is automatic; use priority for interruption rules
- Audio occlusion uses line trace from sound origin to player
- All audio preset transitions are smooth crossfades, never cuts
- PerformanceScaler can disable spatial audio and reduce layer count on low-end
- Renamed from `BPC_AudioManager` to `BPC_AudioAtmosphereController` per Master naming convention.
- Cross-references updated: `BPC_AtmosphereController``BPC_AtmosphereStateController`, `BPC_PlayerMetricsTracker` → updated path, `BPC_CheckpointSystem``BP_Checkpoint`, `BPC_DialoguePlayback``BPC_DialoguePlaybackSystem`.

View File

@@ -0,0 +1,119 @@
# BPC_LightEventController — Light Event Controller
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) — Atmosphere state receiver
- [`BPC_FearSystem`](90_BPC_FearSystem.md) — Fear-driven lighting modulation
- [`BPC_PerformanceScaler`](91_BPC_PerformanceScaler.md) — Performance budget input
### Purpose
Manages all dynamic lighting in the game world including global color grading, fog, shadows, emissive materials, point light intensity modulation, and lighting transitions. Receives preset keys from AtmosphereStateController and applies them with smooth crossfades. Supports fear-reactive lighting effects (flicker, desaturation, shadow intensity) based on the player's fear state.
### Variables
| Name | Type | Description |
|------|------|-------------|
| `ActivePresetKey` | FName | Currently applied lighting preset |
| `TargetPresetKey` | FName | Preset being transitioned to |
| `CurrentColorGrading` | FPostProcessSettings | Active post-process |
| `CurrentFogDensity` | Float | Active exponential fog density |
| `CurrentShadowIntensity` | Float | Shadow darkness bias |
| `LightFlickerIntensity` | Float (0.01.0) | Fear-driven flicker effect |
| `LightFlickerFrequency` | Float | Flicker speed |
| `bEnableFearReactiveLights` | Bool | Fear modulation toggle |
| `TransitionDuration` | Float | Current crossfade duration |
| `TransitionProgress` | Float (0.01.0) | Crossfade interpolation |
| `RegisteredLights` | TArray<ULightComponent> | Dynamic lights to modulate |
| `DesaturationAmount` | Float (0.01.0) | Fear-driven color loss |
| `VignetteIntensity` | Float | Post-process vignette |
| `bIsInDarkVolume` | Bool | Inside darkness volume |
### Functions
| Name | Inputs | Outputs | Description |
|------|--------|---------|-------------|
| `Initialize` | — | — | Cache references, bind events |
| `SetLightingPreset` | PresetKey: FName, TransitionTime: Float | — | Queue preset transition |
| `ApplyPresetDirect` | PresetKey: FName | — | Instant preset application |
| `GetCurrentPresetKey` | — | FName | Current active preset |
| `RegisterDynamicLight` | Light: ULightComponent | — | Add to modulation list |
| `UnregisterDynamicLight` | Light: ULightComponent | — | Remove from modulation list |
| `SetFearFlicker` | Intensity: Float, Frequency: Float | — | Apply flicker to all registered lights |
| `SetDesaturation` | Amount: Float | — | Post-process color loss |
| `SetVignette` | Intensity: Float | — | Post-process vignette |
| `SetShadowIntensity` | Intensity: Float | — | Global shadow bias |
| `SetFogDensity` | Density: Float | — | Exponential fog density |
| `EnterDarkVolume` | — | — | Enter darkness zone |
| `ExitDarkVolume` | — | — | Leave darkness zone |
| `FearReactiveUpdate` | FearLevel: Float, Threshold: EFearThreshold | — | Per-tick fear modulation |
### Blueprint Flow
```
[Tick]
└─► If TransitionProgress < 1.0:
TransitionProgress += DeltaTime / TransitionDuration
Lerp all lighting properties between current and target
On complete: ActivePresetKey = TargetPresetKey
└─► If bEnableFearReactiveLights:
FearReactiveUpdate(CurrentFearLevel, CurrentThreshold)
[SetLightingPreset]
└─► If TransitionTime > 0:
TargetPresetKey = PresetKey
TransitionDuration = TransitionTime
TransitionProgress = 0.0
Begin lerp from current to target
└─► Else:
ApplyPresetDirect(PresetKey)
[FearReactiveUpdate]
└─► Based on fear threshold:
Calm: No lighting change
Uneasy: Subtle light flicker (intensity 0.1), no color change
Nervous: Light flicker (0.3), slight desaturation (0.1)
Afraid: Heavy flicker (0.6), desaturation (0.3), vignette (0.2)
Terrified: Max flicker (0.8), desaturation (0.5), vignette (0.4), shadow intensify
Panic: Strobe flicker, heavy desaturation (0.7), max vignette
└─► Apply to all RegisteredLights:
For each ULightComponent:
If flicker enabled: randomize Intensity between 0..Original* (1 - LightFlickerIntensity)
└─► Apply post-process:
SetDesaturation(DesaturationAmount)
SetVignette(VignetteIntensity)
[EnterDarkVolume]
└─► bIsInDarkVolume = true
└─► Override current preset with Darkness preset
└─► Disable fear-reactive lights during dark volume
└─► DarkVolume exclusive: very low ambient, high fog, long shadows
[ExitDarkVolume]
└─► bIsInDarkVolume = false
└─► Restore previous lighting preset
└─► Re-enable fear-reactive lights
```
### Communications With
| Target | Method | Why |
|--------|--------|-----|
| [`BPC_AtmosphereStateController`](BPC_AtmosphereStateController.md) | Called by | Receive lighting preset keys |
| [`BPC_FearSystem`](90_BPC_FearSystem.md) | Get Owner Component | Fear level for reactive lighting |
| [`BPC_PerformanceScaler`](91_BPC_PerformanceScaler.md) | Direct call | Reduce light quality on low spec |
| [`BP_Checkpoint`](../05-saveload/BP_Checkpoint.md) | Event | Restore lighting on load |
### Reuse Notes
- Works with any ULightComponent (point, spot, directional, rect)
- Post-process settings are blended, not stomped
- Dark volumes override atmosphere-driven lighting
- Fear-reactive lighting is additive on top of current preset
- PerformanceScaler can disable dynamic lighting and fear flicker on low-end hardware
- Renamed from `BPC_LightingManager` to `BPC_LightEventController` per Master naming convention.
- Cross-references updated: `BPC_AtmosphereController``BPC_AtmosphereStateController`, `BPC_CheckpointSystem``BP_Checkpoint`.

View File

@@ -0,0 +1,203 @@
# BPC_MemoryDriftSystem — Memory Drift System
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- **Requires:** [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) — Receives drift trigger requests
- **Requires:** [`DA_RoomMutation`](../14-data-assets/DA_RoomMutation.md) — Mutation definitions per room
- **Required By:** World room actors — Mutations applied to specific rooms
- **Required By:** [`GS_CoreGameState`](../01-core/06_GS_CoreGameState.md) — Tracks active mutations per session
- **Engine/Plugin Requirements:** GameplayTags (room tags, mutation tags), Level Streaming
### Purpose
Manages the "room swap" or "memory drift" effect — subtle environmental changes to rooms the player has already visited. Tracks room visit history and triggers mutations (moved objects, changed lighting, new content appearing) based on adaptive director input and story progression.
---
## 1. Enums
*Uses GameplayTags for room and mutation identification. No new enums defined.*
---
## 2. Structs
### `S_RoomVisitData`
| Field | Type | Description |
|-------|------|-------------|
| `RoomTag` | GameplayTag | Identifier for the room/area |
| `VisitCount` | Integer | Number of times player has entered this room |
| `LastVisitTime` | Float | Game time of most recent visit |
| `ActiveMutationTag` | GameplayTag | Currently active mutation in this room (None if unchanged) |
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `MutationRegistry` | Array of DA_RoomMutation | Empty | MutationConfig | Available mutations for all rooms |
| `DriftIntensity` | Float | 0.2 | MutationConfig | Global mutation intensity 0.01.0 (0=none, 1=max) |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `VisitedRooms` | Map (GameplayTag → S_RoomVisitData) | Empty | Internal | Per-room visit history |
| `ActiveMutations` | Set of GameplayTag | Empty | Internal | Currently active mutation tags |
---
## 4. Functions
### Public Functions
#### `RegisterRoomVisit` → `void`
- **Description:** Called when the player enters a room. Updates visit count and timestamp.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `RoomTag` | GameplayTag | Room being entered |
- **Blueprint Authority:** Any
- **Flow:** Check if RoomTag in VisitedRooms → if yes, increment VisitCount → if no, create new entry → update LastVisitTime
#### `TriggerDrift` → `void`
- **Description:** Triggers a room mutation for a specific room or the most recently revisited room.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `RoomTag` | GameplayTag | Target room (None = select automatically) |
| `Intensity` | Float | Override intensity for this drift |
- **Blueprint Authority:** Any
- **Flow:** Select mutation from registry → apply to target room → add to ActiveMutations → broadcast OnDriftTriggered
#### `SelectMutation` → `DA_RoomMutation`
- **Description:** Picks an appropriate mutation for a room based on visit count, drift intensity, and story state.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `RoomTag` | GameplayTag | Room to mutate |
- **Blueprint Authority:** Any
- **Flow:** Filter MutationRegistry by RoomTag → filter by eligibility (visit count >= min visits, drift intensity >= min required) → select highest priority eligible mutation
#### `GetVisitedRooms` → `Array of S_RoomVisitData`
- **Description:** Returns all room visit records for external analysis.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `GetActiveMutations` → `Set of GameplayTag`
- **Description:** Returns all currently active mutation tags.
- **Parameters:** None
- **Blueprint Authority:** Any
#### `ResetRoom` → `void`
- **Description:** Clears mutations from a room, restoring its original state.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `RoomTag` | GameplayTag | Room to reset |
- **Blueprint Authority:** Any
#### `SetDriftIntensity` → `void`
- **Description:** Updates the global drift intensity.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `NewIntensity` | Float | 0.01.0 |
- **Blueprint Authority:** Any
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnDriftTriggered` | RoomTag: GameplayTag, MutationTag: GameplayTag | Public | A room mutation was applied |
| `OnDriftCleared` | RoomTag: GameplayTag | Public | A room's mutations were cleared |
| `OnRoomFirstVisit` | RoomTag: GameplayTag | Public | First time player enters a room |
| `OnRoomRevisit` | RoomTag: GameplayTag, VisitCount: Integer | Public | Player re-enters a previously visited room |
---
## 6. Overridden Events / Custom Events
### Event: `BeginPlay`
- **Description:** Initialises mutation registry from level data and subscribes to room trigger volumes.
- **Flow:**
1. Load MutationRegistry for current level
2. Register room trigger volume overlap events
3. Set initial DriftIntensity from game settings
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[Player enters room trigger] --> B[RegisterRoomVisit RoomTag]
B --> C{First visit?}
C -->|Yes| D[Create new S_RoomVisitData]
C -->|No| E[Increment VisitCount]
D --> F[Broadcast OnRoomFirstVisit]
E --> G[Broadcast OnRoomRevisit]
F --> H
G --> H{Due for mutation?}
H -->|Yes| I[SelectMutation for RoomTag]
H -->|No| J[Wait for next visit]
I --> K{Mutation eligible?}
K -->|Yes| L[Apply mutation to room]
K -->|No| J
L --> M[Add to ActiveMutations]
M --> N[Broadcast OnDriftTriggered]
O[AdaptiveDirector requests drift] --> P[TriggerDrift target room]
P --> I
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| Room trigger volumes | Overlap event | `RoomTag: GameplayTag` |
| [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) | Direct call | `TriggerDrift(RoomTag, Intensity)` |
| [`DA_RoomMutation`](../14-data-assets/DA_RoomMutation.md) | Data asset read | Mutation definition: what to change, how, conditions |
| World room actors | Direct call | Apply mutation changes to specific room |
| [`GS_CoreGameState`](../01-core/06_GS_CoreGameState.md) | Direct write | Active mutation set for save persistence |
---
## 9. Validation / Testing Checklist
- [ ] S_RoomVisitData struct has all 4 fields
- [ ] RegisterRoomVisit correctly tracks first vs repeat visits
- [ ] VisitedRooms map updates VisitCount and LastVisitTime correctly
- [ ] SelectMutation filters by room tag and eligibility conditions
- [ ] TriggerDrift applies mutation and broadcasts OnDriftTriggered
- [ ] ResetRoom clears mutations and restores room to original state
- [ ] DriftIntensity gates which mutations are eligible
- [ ] Edge case: player revisits room many times → mutations escalate progressively
- [ ] Edge case: no eligible mutations for room → no change made
- [ ] Edge case: rapid room switching → visit counts tracked independently per room
---
## 10. Reuse Notes
- MutationRegistry is populated per level — each level defines which rooms can mutate and how
- DriftIntensity is ramped up by the adaptive director as the game progresses
- For non-horror games, leave MutationRegistry empty to disable the system
- Room tags are defined in GI_GameTagRegistry — add project-specific rooms there
- Extend DA_RoomMutation with new mutation types (actor spawn, light change, sound trigger, etc.)
---
*Specification based on Master Section 9.3, line 2760.*

View File

@@ -0,0 +1,197 @@
# BPC_PacingDirector — Pacing Director
## Blueprint Spec — UE 5.55.7
---
### Parent Class
`ActorComponent`
### Dependencies
- **Requires:** [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) — Receives pacing guidance requests
- **Required By:** [`BPC_ScareEventSystem`](BPC_ScareEventSystem.md) — Scare scheduling governed by tension curve
- **Required By:** [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) — Music intensity driven by tension (via `UpdateGameplayParameter("MusicIntensity")`)
- **Engine/Plugin Requirements:** GameplayTags, CurveFloat asset
### Purpose
Monitors overall session pacing: tension curves, quiet beats, and escalation patterns. Prevents scare fatigue and intensity burnout by enforcing structured peaks and valleys in tension. Uses a designer-authored CurveFloat to define ideal tension over session time and actively measures/steers actual tension toward the curve.
---
## 1. Enums
*No new enums defined. Uses GameplayTags for tension sources.*
---
## 2. Structs
*No new structs defined.*
---
## 3. Variables
### Configuration (Instance Editable, Expose On Spawn)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `TensionCurve` | CurveFloat | None | PacingConfig | Designer-authored curve: ideal tension (01) over session time (seconds) |
| `QuietBeatMinInterval` | Float | 120.0 | PacingConfig | Minimum seconds between quiet beat enforcement |
### Internal (Private / Protected, No Expose)
| Variable | Type | Default | Category | Description |
|----------|------|---------|----------|-------------|
| `CurrentTension` | Float | 0.0 | Internal | Actual measured tension (0.01.0) |
| `TensionSources` | Map (GameplayTag → Float) | Empty | Internal | Contributing tension sources and their values |
| `LastQuietBeatTime` | Float | 0.0 | Internal | Game time of last enforced quiet beat |
| `SessionElapsedTime` | Float | 0.0 | Internal | Accumulated session play time |
---
## 4. Functions
### Public Functions
#### `SetTensionSource` → `void`
- **Description:** Registers or updates a tension source. Recalculates CurrentTension as the maximum of all active sources.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `SourceTag` | GameplayTag | Identifier for the tension source |
| `Value` | Float | Tension contribution 0.01.0 |
- **Blueprint Authority:** Any
- **Flow:** Add/update in TensionSources → recalculate CurrentTension = max of all source values → check against TensionCurve
#### `RemoveTensionSource` → `void`
- **Description:** Removes a tension source and recalculates current tension.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `SourceTag` | GameplayTag | Source to remove |
- **Blueprint Authority:** Any
#### `GetCurrentTension` → `Float`
- **Description:** Returns the current measured tension level (0.01.0).
- **Parameters:** None
- **Blueprint Authority:** Any
#### `GetIdealTension` → `Float`
- **Description:** Reads the TensionCurve at the current session time to get the ideal tension target.
- **Parameters:** None
- **Blueprint Authority:** Any
- **Flow:** Evaluate TensionCurve at SessionElapsedTime → return value
#### `ShouldEscalate` → `Bool`
- **Description:** Returns true if actual tension is below the ideal curve (room to escalate). False if tension exceeds curve (should back off).
- **Parameters:** None
- **Blueprint Authority:** Any
- **Flow:** Compare CurrentTension to GetIdealTension()
#### `ShouldQuietBeat` → `Bool`
- **Description:** Returns true if a quiet beat should be enforced to prevent sustained high tension.
- **Parameters:** None
- **Blueprint Authority:** Any
- **Flow:** Check if CurrentTension > 0.8 AND time since LastQuietBeatTime > QuietBeatMinInterval
#### `EnforceQuietBeat` → `void`
- **Description:** Forces tension down to a low level temporarily. Clears or reduces active tension sources.
- **Parameters:**
| Param | Type | Description |
|-------|------|-------------|
| `Duration` | Float | How long the quiet beat lasts |
- **Blueprint Authority:** Any
- **Flow:** Reduce high tension sources → update LastQuietBeatTime → broadcast OnQuietBeatEnforced
#### `GetTensionDelta` → `Float`
- **Description:** Returns the difference between ideal and actual tension (positive = too low, negative = too high).
- **Parameters:** None
- **Blueprint Authority:** Any
---
## 5. Event Dispatchers
| Dispatcher | Parameters | Bind Access | Description |
|------------|-----------|-------------|-------------|
| `OnTensionChanged` | CurrentTension: Float, IdealTension: Float | Public | Tension level changed |
| `OnEscalationNeeded` | Delta: Float | Public | Tension below curve — escalate recommended |
| `OnDeescalationNeeded` | Delta: Float | Public | Tension above curve — back off recommended |
| `OnQuietBeatEnforced` | Duration: Float | Public | Quiet beat enforced |
---
## 6. Overridden Events / Custom Events
### Event: `Tick`
- **Description:** Updates session elapsed time and evaluates pacing against the tension curve.
- **Flow:**
1. SessionElapsedTime += DeltaTime
2. GetIdealTension from curve
3. Compare CurrentTension to IdealTension
4. If delta significant → broadcast appropriate signal (escalation/deescalation)
5. Check ShouldQuietBeat → enforce if needed
---
## 7. Blueprint Graph Logic Flow
```mermaid
flowchart TD
A[Tick] --> B[SessionElapsedTime += DeltaTime]
B --> C[IdealTension = TensionCurve.Evaluate SessionElapsedTime]
C --> D[Calculate TensionDelta = IdealTension - CurrentTension]
D --> E{abs TensionDelta > threshold?}
E -->|No| F[No action needed]
E -->|Yes| G{TensionDelta > 0?}
G -->|Yes - below curve| H[Broadcast OnEscalationNeeded]
G -->|No - above curve| I[Broadcast OnDeescalationNeeded]
H --> J[AdaptiveDirector escalates difficulty/intensity]
I --> K[AdaptiveDirector reduces difficulty/intensity]
L[Check ShouldQuietBeat] --> M{CurrentTension > 0.8 AND elapsed > QuietBeatMinInterval?}
M -->|Yes| N[EnforceQuietBeat Duration]
N --> O[Broadcast OnQuietBeatEnforced]
M -->|No| P[Continue monitoring]
```
---
## 8. Communication Matrix
| Who Talks | How | What Is Sent |
|-----------|-----|-------------|
| [`BPC_ScareEventSystem`](BPC_ScareEventSystem.md) | Direct read | `ShouldEscalate()` — determines if scare events should fire |
| [`SS_AudioManager`](../10-adaptive/132_SS_AudioManager.md) | Direct call | `UpdateGameplayParameter("MusicIntensity")``CurrentTension: Float` |
| [`BPC_AdaptiveEnvironmentDirector`](BPC_AdaptiveEnvironmentDirector.md) | Dispatcher | `OnEscalationNeeded`, `OnDeescalationNeeded` |
| All tension-contributing systems | Direct call | `SetTensionSource(Tag, Value)` — AI presence, stress, atmosphere, combat |
---
## 9. Validation / Testing Checklist
- [ ] TensionCurve asset is assigned and valid
- [ ] SetTensionSource correctly updates TensionSources map and recalculates CurrentTension
- [ ] RemoveTensionSource removes entry and recalculates
- [ ] CurrentTension is always max of all active sources (or 0 if none)
- [ ] GetIdealTension reads correct value from curve at current time
- [ ] ShouldEscalate returns true when below curve, false when above
- [ ] ShouldQuietBeat enforces periodic relief during sustained high tension
- [ ] EnforceQuietBeat reduces tension and updates LastQuietBeatTime
- [ ] Edge case: no TensionCurve assigned → system runs with neutral pacing
- [ ] Edge case: all sources removed → CurrentTension drops to 0
---
## 10. Reuse Notes
- TensionCurve is the primary designer tool for pacing — author different curves per level/chapter
- TensionSources map allows any system to contribute to tension without coupling
- QuietBeatMinInterval prevents scare fatigue; tune per project for desired horror rhythm
- For non-adaptive games, leave TensionCurve unassigned and the system passes through neutral
- SessionElapsedTime resets on level load; use persistent elapsed time for full-session pacing
---
*Specification based on Master Section 9.8, line 2905.*

View File

@@ -0,0 +1,194 @@
# BPC_PlaystyleClassifier — Playstyle Classifier
## Blueprint Spec — UE 5.55.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.*