# Developer Reference — Capture Systems (Systems 136-147)

**Version:** 1.0 | **Systems:** 136-147 (12 systems) | **Phase:** 17 | **Layer:** Rendering & Visual

---

## Architecture Overview

The Planar Capture System provides a unified rendering pipeline for mirrors, portals, monitors, and horror surfaces. All performance-critical work (camera math, render target management, scene capture lifecycle, quality budget enforcement) runs in C++, while designer-facing configuration, event scripting, and material authoring live in Blueprint.

### System Layers

```
┌──────────────────────────────────────────────────────────────────────┐
│  BLUEPRINT CONTENT LAYER                                              │
│  BP_Mirror → BP_HorrorMirror     Designer places in level, configures │
│  BP_Portal → BP_Monitor → BP_FakeWindow                               │
│  DA_PlanarCaptureProfile → MPC_CaptureSurface → MI_* material instances│
├──────────────────────────────────────────────────────────────────────┤
│  C++ RUNTIME LAYER                                                    │
│  USS_PlanarCaptureManager     ← Global budget, RT pool, scoring (WorldSubsystem + FTickable) │
│  ABP_PlanarCaptureActor       ← Surface mesh, MDI, registration      │
│  UBPC_PlanarCapture           ← SceneCapture2D, camera math, horror  │
│  UPlanarCaptureCameraUtils    ← Static math: mirror, portal, oblique │
├──────────────────────────────────────────────────────────────────────┤
│  UE5 ENGINE LAYER                                                     │
│  USceneCaptureComponent2D  UTextureRenderTarget2D                    │
│  UMaterialParameterCollectionInstance  UWorldSubsystem               │
└──────────────────────────────────────────────────────────────────────┘
```

---

## Data Flow: Mirror Reflection

### Step-by-Step: Player Looks at a Mirror

1. **Registry:** `BP_Mirror.BeginPlay()` calls `SS_PlanarCaptureManager.RegisterSurface(this)` — the mirror is now tracked globally.
2. **Evaluation (0.5s interval):** `SS_PlanarCaptureManager.Tick()` calls `EvaluateAllSurfaces()`.
3. **Scoring:** For each registered surface, `BPC_PlanarCapture.GetCurrentScore()` computes:
   - Screen coverage (how much screen space does the surface occupy?)
   - Facing angle (is the player looking at the surface or edge-on?)
   - Distance to viewer
   - Scripted priority override
   - Visibility frustum check
4. **Tier Assignment:** Based on composite score and budget limits, the manager calls `BPC_PlanarCapture.ApplyQualityTier(Tier)`.
5. **Capture Tick (per frame):** `BPC_PlanarCapture.TickComponent()` checks time since last capture vs the quality tier's `CaptureInterval`. If it's time to capture:
   - Gets the player camera transform
   - Calls `ComputeCaptureCameraTransform()` — for Mirror mode, this calls `UPlanarCaptureCameraUtils::ComputeMirroredTransform()` which reflects the player camera across the mirror plane
   - Sets the `USceneCaptureComponent2D` world transform
   - Calls `SceneCapture->CaptureScene()` — renders to the allocated `UTextureRenderTarget2D`
   - Calls `PushMPCParameters()` to update the MPC with current horror/visual params
6. **Material Display:** `M_CaptureSurface_Master` samples the render target texture, applies UV flip (for mirror), and layers any active effects (dirt, steam, condensation). The output is displayed on the surface mesh.

### Quality Tier Transitions

```
  Off ←──→ Low ←──→ Medium ←──→ High ←──→ Hero
  (score=0)  (>0)    (≥0.2)     (≥0.5)    (≥0.8)

  Demotion happens when:
  - Score drops below threshold (player walks away)
  - Budget limit exceeded (too many Hero surfaces)
  - GlobalQualityCap clamps tier
  - Force tier override is released

  Promotion happens when:
  - Score rises above threshold (player approaches)
  - Budget slots open up (higher-tier surface destroyed or goes Off)
  - Scripted priority boost kicks in
```

---

## State Machine: Quality Tier Lifecycle

```
  [Surface Spawns]
       │
       ▼
  ┌─────────┐     score > 0     ┌─────────┐
  │   OFF   │ ────────────────► │   LOW   │
  └─────────┘                   └────┬────┘
       ▲                             │ score ≥ 0.2
       │ score = 0              ┌────▼────┐
       │ or !inFrustum          │ MEDIUM  │
       │                        └────┬────┘
       │                             │ score ≥ 0.5
       │                        ┌────▼────┐
       │                        │  HIGH   │
       │                        └────┬────┘
       │                             │ score ≥ 0.8
       │                        ┌────▼────┐
       │                        │  HERO   │
       │                        └─────────┘
       │
  ┌────┴────────┐
  │ SURFACE      │  Called on EndPlay, DisableSurface, or DestroySurface
  │ DESTROYED    │
  └─────────────┘
```

---

## Budget Enforcement

The `SS_PlanarCaptureManager` enforces three levels of budget:

1. **Per-Tier Count Limits:** `MaxHeroSurfaces` (default 1), `MaxHighSurfaces` (3), `MaxMediumSurfaces` (6). If 4 surfaces score ≥ 0.5, only the top 3 get High tier — the 4th gets Medium even if it scored for High.
2. **Total Memory Budget:** `MaxTotalRenderTargetMemoryMB` (default 128MB). If exceeded, the manager logs a warning. Future: automatic demotion of lowest-priority surfaces until under budget.
3. **Global Quality Cap:** `GlobalQualityCap` (default High). No surface exceeds this tier regardless of score. Set to Medium on Steam Deck / lower hardware.

---

## Render Target Pool

The pool reduces memory allocation overhead by reusing render targets:

```
RequestRenderTarget(Size):
  1. Search pool for entry with matching Size AND !bInUse
  2. If found → mark bInUse=true, return RT
  3. If not → CreateRenderTarget(Size) → add to pool

ReleaseRenderTarget(RT):
  1. Find pool entry for this RT
  2. Mark bInUse=false (RT stays allocated, ready for next request)
```

This means the pool stabilizes after initial allocation — no new RTs are created unless a new size is requested.

---

## Horror Features Deep Dive

### Wrong Reflection (ActivateHorrorReflection)
```
1. UBPC_PlanarCapture saves current ShowOnlyActors → SavedShowOnlyActors
2. ShowOnlyActors is cleared
3. WrongReflectionActor is added to ShowOnlyActors
4. UpdateActorLists() pushes to USceneCaptureComponent2D::ShowOnlyActors
5. Next capture frame: SceneCapture2D now ONLY renders the WrongReflectionActor
6. Material: WrongReflectionBlend MPC param crossfades between normal RT and wrong reflection
```

### Delayed Frame Ring Buffer
```
Hero tier with DelayedFrameCount = 5:
1. FrameRingBuffer array has 5 render targets
2. Each capture frame: RingBufferWriteIndex = (index + 1) % 5
3. Material: DelayedReflectionBlend MPC param blends between current frame and oldest ring buffer frame
4. Effect: Player's reflection lags 5 frames behind — unsettling
```

### Steam Text Reveal
```
TriggerHorrorScare() → Timeline → TextRevealProgress MPC param ramps 0→1
Material Layer 6: TextRevealMask texture is wiped from 0 to 1
Appearance: "HELP ME" appears to write itself in the steam on the mirror
```

---

## Integration Points

### BPC_StateManager (130)
```cpp
// Portal teleport must be gated
if (!StateManager->IsActionPermitted(FGameplayTag::RequestGameplayTag("Framework.Action.Teleport")))
    return; // Blocked — player is dead, in cutscene, etc.
```

### BPC_ScareEventSystem (101)
```cpp
// Horror mirror triggers coordinated scare
ScareEventSystem->TriggerScareEvent(ScareEventTag);
// This coordinates lights (96), audio (132), pacing (98), stress (10)
```

### SS_AudioManager (132)
```cpp
// All surface audio routes through audio subsystem
AudioManager->PlaySoundAtLocation(MirrorShatterCue, GetActorLocation());
AudioManager->PlaySoundAtLocation(PortalWhooshCue, GetActorLocation());
```

### I_Persistable (36)
```cpp
// Surface state persists across saves
// BP_PlanarCaptureActor implements I_Persistable:
CollectState() → { bIsDestroyed, CurrentDirtLevel, CurrentSteamLevel }
RestoreState() → { Apply saved state }
```

### SS_EnhancedInputManager (128)
```cpp
// Portal transition switches input context
InputManager->PushContext(PortalTransitionContext, EInputContextPriority::Inspection);
// Player exits portal → PopContext
```

---

## Multiplayer Networking

### What Replicates
- **`bRepIsActive`** on `BP_PlanarCaptureActor` — server tells all clients whether a surface is active (e.g., a mirror was shattered by another player)

### What Does NOT Replicate (Local-Only)
- **All capture rendering** — each client renders their own view with their own camera perspective. There is zero reason to replicate render targets.
- **Quality tiers** — each client evaluates surfaces independently (different camera positions mean different scores)
- **Horror effects** — triggered server-side (scare event), executed locally on each client

### Server-Authoritative Pattern
```
Server: BP_Mirror.DestroySurface() [HasAuthority]
  → Set bRepIsActive = false
  → OnRep_IsActive fires on all clients
  → Each client independently: DisableSurface() → ShutdownCapture()
```

---

## Performance Characteristics

| Tier | GPU Cost (relative) | VRAM per Surface | CPU Cost |
|------|---------------------|------------------|----------|
| Hero | 16x baseline | 16 MB | High (60 captures/sec) |
| High | 4x baseline | 4 MB | Medium (30/sec) |
| Medium | 1x baseline | 1 MB | Low (15/sec) |
| Low | 0.25x baseline | 256 KB | Minimal (4/sec) |
| Off | 0 | 0 | Zero |

### Optimization Tips
1. **MaxCaptureDistance:** Set lower (3000-5000) for indoor levels — mirrors far away go Off
2. **FullEvaluationInterval:** Increase to 1.0s for large levels with many mirrors to reduce CPU scoring cost
3. **GlobalQualityCap:** Set to Medium on Steam Deck. Set to High on consoles.
4. **ShowOnly lists:** Use aggressively. Capturing only 5 actors is vastly cheaper than capturing 500.
5. **Monitor FPS:** Monitors should use 5fps Low tier — they don't need real-time updates.
6. **Lumen on capture:** Only enable on Hero tier. Each Lumen-enabled capture is ~3x more expensive.

---

## Debugging

### Console Commands
```
// Show all registered capture surfaces
SS_PlanarCaptureManager.GetSurfaceCount()

// Show pool memory usage
SS_PlanarCaptureManager.GetPoolMemoryUsageMB()

// Force all to max quality (for visual debugging)
SS_PlanarCaptureManager.ForceAllSurfacesToTier(Hero)
SS_PlanarCaptureManager.ReleaseForceTier()

// Show capture FPS (add to BPC_PlanarCapture debug mode)
stat SceneRendering
```

### Visual Debug
- **Unlit view mode:** See raw render target output without material effects
- **Wireframe:** Verify camera frustum on SceneCaptureComponent2D
- **Stat GPU:** SceneCapture passes appear under "SceneCapture" category

---

## Build Order (Phase 17)

| Sub-Phase | Systems | Dependencies |
|-----------|---------|-------------|
| 17a — C++ Core | 136, 137, 138, `PlanarCaptureCommon`, `PlanarCaptureCameraUtils` | Renderer module |
| 17b — Materials | 144, 145, 147 | 17a (for MPC parameter names) |
| 17c — Blueprint Actors | 139, 140, 141, 142, 143 | 17a + 17b |
| 17d — Data Assets | 146 | 17a |
| 17e — Integration | Wire to 101, 132, 130, 128, 36 | 17c |

---

*Developer Reference — Capture Systems v1.0. Companion to Blueprint Spec files in `docs/blueprints/17-capture/`.*
