# 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 │ │ ASS_PlanarCaptureManager ← Global budget, RT pool, scoring │ │ 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/`.*