Tactical Action · Architecture

Presentation Pipelines: Audio, VFX, Cinematics & UI

A focused page within the Tactical Action Architecture documentation. The full map and every sibling page live in the Architecture hub.

8sections12 minread1diagram

On this page

V4 has to sound, spark, cut, and read like six games at once: the brassy bombast of a CoD set-piece, the sparse noir pulse of a Splinter Cell infiltration, the erhu-and-orchestra swell of a Wukong boss, the diegetic strings of a Commandos op, the modular sci-fi of an RTS skirmish, and the synth-rock of a Contra run — all emitted by one shared presentation stack. Four Unreal C++ modules own that job: V4Audio (MetaSounds patches, dynamic music stacks, voice barks, foley, spatialization, the karaoke rhythm mode), V4VFX (Niagara muzzle flashes, blood decals, debris, weather, Wukong combat auras), V4Cinematics (Sequencer-driven openers/boss-intros/finishers, the slow-mo "showdown" governor, Movie Render Queue profiles, director commentary), and V4UI (the CommonUI/MVVM front-end shell, six per-cell HUDs, the stealth light-gauge widget, and the full accessibility contract). Where the combat and netcode layers must be provably correct (./networking-determinism.md), this layer has the inverse remit: it is cosmetic, it may be as expensive as the platform budget allows, and its only hard rule is that it must never feed back into the integer state the lockstep and rollback paths hash.

What actually lives in the repo is the decision, policy, and validation layer for that stack — built as four UGameInstanceSubsystems plus a handful of components and view-models, every one of which compiles and is exercised by automation — sitting above a set of engine plugins that are linked but, with two honest exceptions, not yet called, and above authored content that ships today as JSON descriptors rather than binary .uasset. This page is part of the Presentation, Production & Launch group; the section hub is ../V4_ARCHITECTURE.md.

What ships, honestly#

The four modules are real, deterministic C++ that builds and is tested, but they are a specification-and-decision layer, not a wired audiovisual runtime. Six qualifications so the rest of the page reads at face value:

  1. The modules link the real presentation plugins. V4Audio.Build.cs:12-19 adds MetasoundEngine, AudioModulation, and AudioGameplayVolume; V4VFX.Build.cs:12-16 adds Niagara; V4Cinematics.Build.cs:12-23 adds MovieScene, MovieRenderPipelineCore, and Sequencer (editor-only); and V4UI.Build.cs:12-21 adds UMG, CommonUI, ModelViewViewModel, and FieldNotification. Linking is not calling.

  2. Only two of the four modules touch an engine runtime at all — and modestly. V4Audio calls IPluginManager::Get().FindPlugin(...) to probe whether a spatialization plugin is installed (V4AudioBootstrapSubsystem.cpp:7), and V4UI's HUD view-model calls UMVVMViewModelBase::BroadcastFieldValueChanged (V4HUDViewModel.cpp:94). Neither V4VFX nor V4Cinematics invokes its plugin at all: there is no UNiagaraFunctionLibrary::SpawnSystem*, no ULevelSequencePlayer, and no SetGlobalTimeDilation anywhere in their source. They are bookkeepers and state machines, not spawners.

  3. There are zero binary .uasset presentation assets. V4/ue/Content holds no .uasset at all; the presentation content directories ship 102 *.uasset.v4asset.json descriptors instead — MetaSounds patches, Niagara systems, level sequences, loading-screen libraries, data assets — none of which a soft-object path resolves to yet. The cue, patch, and sequence soft paths the C++ builds (/Game/V4VFX/Niagara/..., /Game/V4Audio/MetaSounds/..., /Game/V4Cinematics/...) name content that is specified but not authored.

  4. The dispatchers do real policy, not real emission. The VFX spawner enforces per-LOD budgets and cooldowns and tracks active counts; it does not put a particle on screen. The cinematic subsystem advances a playhead and flips a state enum; it does not drive a Sequencer. The slow-mo subsystem computes an EffectiveTimeDilation curve; it does not slow the world. The gate is the product here — the engine hand-off is the pending integration.

  5. The decision math is genuine and value-tested. Footstep attenuation, spatial occlusion gain, decibel ducking, rhythm-timing judgement, slow-mo blends, and MVVM change-notification are real algorithms asserted against specific computed numbers across 16 automation specs under V4Tests/Private/{V4AudioTests,V4VFXTests,V4CinematicsTests,V4UITests,V4AccessibilityTests}. They would fail against a hardcoded return.

  6. Authored data sidecars are real and locked. V4/ui/launch-experience/launch-experience.json and the six V4/cinematics/*.json commentary manifests (2,148 lines) are schema-versioned feature contracts with owners and QA-coverage lists — design data, not code, and honestly labeled as such.

Everything below is real where it cites a function and a line; where it names an authored asset or an un-called plugin, that is the specified-but-pending part.

The shared shape: four subsystems, one discipline#

All four modules are built the same way and for the same reason. Each is a UGameInstanceSubsystem (or, for V4UI, a small family of subsystems plus a view-model and widget) that holds registered catalogs in TMaps and exposes three kinds of entry point: Build…/Default… factories that emit launch manifests, Validate… self-checks that fail loud with a TArray<FString> of errors, and Resolve…/Dispatch…/Judge… decisions that are pure functions of their inputs. That split lets every rule be unit-tested without a live world and keeps the math side-effect-free, so the same call yields the same answer on every machine — the same determinism hygiene the gameplay core demands, applied to the cosmetic layer.

flowchart TD subgraph Decision["V4 presentation — real, tested C++ (this page)"] AUD["V4Audio<br/>MusicStack · VoiceBark · Foley · Spatial · Bootstrap"] VFX["V4VFX<br/>VFXSpawner (budget + cooldown gate)"] CIN["V4Cinematics<br/>Cinematic state machine + SlowMo governor"] UI["V4UI<br/>MVVM view-model · per-cell HUDs · accessibility"] end GAS["GAS verbs / combat / stealth"] --> AUD & VFX & CIN UI -->|reads, never writes| GAS AUD -. "IPluginManager probe (real)" .-> PLUG["SteamAudio / Resonance"] UI -. "BroadcastFieldValueChanged (real)" .-> FN["FieldNotification"] AUD -. soft path .-> MS["MetaSounds .uasset — not authored"] VFX -. soft path .-> NS["Niagara .uasset — not authored"] CIN -. soft path .-> LS["LevelSequence .uasset — not authored"] UI -. soft class .-> WBP["WBP_* widgets — not authored"]

The dashed edges are the boundary: two are real engine-runtime touches, the rest are soft references awaiting authored content and a spawn/playback call site.

Audio — V4Audio#

Runtime mixers: music, barks, foley, spatial#

Four small subsystems and a component do the live audio decisions, each with its own automation spec.

Dynamic music stacks. UV4MusicStackSubsystem holds a per-cell stack of FV4MusicLayers and resolves a blend target by summing the active layers' volumes and clamping to unity: GetCellTargetVolume (V4MusicStackSubsystem.cpp:43) returns FMath::Clamp(Σ active TargetVolume, 0, 1). Toggling a layer (SetLayerActive) is how a cell transitions combat-to-ambient without restarting the bed (V4.Audio.MusicStack.Layering).

Voice barks with cooldown. UV4VoiceBarkSubsystem::DispatchBark (V4VoiceBarkSubsystem.cpp:14) keys barks Operator.Bark, and refuses a dispatch when CurrentTime − LastDispatch < CooldownSeconds — a genuine per-line rate limiter, not a coin-flip (V4.Audio.VoiceBark.DispatchCooldown).

Stance-aware foley. UV4FoleyComponent::ResolveFootstepVolume (V4FoleyComponent.cpp:21) multiplies a per-surface volume by a stance multiplier — Prone 0.25, Crouch 0.55, Sprint 1.25, Stand 1.0 (:41-55) — and by a speed ratio clamped to [0.2, 1.5], then clamps the product to [0, 2]. This is the audible side of the stealth sound-footprint (./ai-perception-stealth.md): crouching is quiet, sprinting is loud (V4.Audio.Foley.StanceAndSurface).

Spatial attenuation. UV4SpatialAudioBridge::ResolveAttenuatedGain (V4SpatialAudioBridge.cpp:51) computes a linear-falloff distance gain clamp(1 − dist/radius, 0, 1) times an occlusion gain 1 − occlusion. It is a real, testable attenuation model that runs without any audio device (V4.Audio.SpatialBridge.AttenuationAndOcclusion).

The bootstrap subsystem: manifests, ducking, providers, rhythm#

UV4AudioBootstrapSubsystem (788 lines) is the catalog and policy hub. It builds the launch manifest the audio team commits to — 110 weapon-foley patches, 40 operators × 120 bark lines = 4,800 bark patches, 4,942 MetaSound patches total, 6 music-stack cells, 12 ambience maps, 12 vehicle profiles — and ValidateLaunchAudioManifest fails if any floor is missed (V4.Audio.Bootstrap.Patches pins every number).

Three pieces are worth reading as genuine domain logic:

  • Decibel ducking. ResolveDuckedMix (:370) converts each rule's DuckAmountDb to a linear multiplier with FMath::Pow(10, min(0, dB)/20) — the correct dB→amplitude law — and applies it to the named target bus, accumulating the applied-rule ids. The default rules duck music −7 dB under voice and SFX −5 dB under a critical callout (:364-367), exactly the "music ducks under VO; SFX ducks under callouts" promise (V4.Audio.Modulation.SnapshotsAndDucking).

  • Honest provider probing. BuildDefaultSpatialProviderProfiles (:426) ties each profile's backing-engine flag to whether the plugin is actually enabled: V4IsPluginEnabled("SteamAudio") and V4IsPluginEnabled("ResonanceAudio") call through IPluginManager (:7-11). The inline comment is explicit — it sets the flag "instead of asserting it unconditionally." This is a fail-loud capability seam, the opposite of a fabricated true.

  • Karaoke rhythm judging. JudgeRhythmInput (:616) computes the timing offset in milliseconds and returns Perfect (≤45 ms → 1000 pts, combo continues), Good (≤110 ms → 500 pts, combo continues), or Miss, reading the per-note windows. The spec drives an input 0.52 s against a 0.50 s beat to Perfect, 0.59 s to Good/500, and 0.70 s to Miss — three outcomes a hardcoded return could not satisfy (V4.Audio.KaraokeRhythmMode). BuildKaraokeRhythmCharts ships three original, streaming-cleared charts, and ValidateKaraokeRhythmMode rejects any chart with fewer than 8 notes, 4 lyric cues, or a missing clearance flag.

VFX — V4VFX#

UV4VFXSpawnerSubsystem is a budget-and-cooldown gate, and that is the honest description of the whole module: it decides whether a Niagara cue may play and records that it did, but never calls Niagara. DispatchCue (V4VFXSpawnerSubsystem.cpp:72) walks four checks in order and returns an EV4VFXDispatchResult:

  1. unknown cue → UnknownCue;
  2. inside the cue's cooldown → CoolingDown;
  3. at the cue's per-cue MaxActiveOverrideBudgetExceeded;
  4. at the LOD-bucket budgetBudgetExceeded;
  5. otherwise increment the active count, append a FV4VFXDispatchRecord, and return Spawned.

The LOD buckets carry real defaults — Hero 32, Medium 96, Background 160 active systems (V4VFXSpawnerSubsystem.h:67-74), with per-bucket emitter caps and tick intervals in BuildDefaultLodBucketPolicies (:250 — Hero allows dynamic lights and ticks every frame; Background caps at 3 emitters and ticks at 0.1 s). ReleaseCueInstance decrements the count so freeing a slot re-opens the budget. The launch library is 22 cues across eight Niagara families (muzzle flash, blood, debris, weather, environment, Wukong combat, RTS projectiles, arcade sprites), each with a family-tuned cooldown (muzzle flash 0.04 s, weather 1.0 s). The V4.VFX.Spawner.DispatchCooldownAndResolve spec asserts the exact Spawned→CoolingDown→Spawned sequence across a cooldown boundary, and V4.VFX.Spawner.LodBudgets asserts the 32/96/160 budgets, a BudgetExceeded when a 1-slot hero bucket is full, and recovery after a release. The pending work is the last inch: turn a Spawned result into a UNiagaraFunctionLibrary call against an authored system.

Cinematics — V4Cinematics#

UV4CinematicSubsystem models Sequencer playback as a state machine over a playhead float, not a live ULevelSequencePlayer. PlayCinematic (:159) returns Started, UnknownCinematic, or AlreadyPlaying; Pause/Resume gate on the current state; and AdvanceCurrentCinematic (:206) clamps the playhead to the duration and flips to Finished when it lands — so Advance(5 s) on a 5-second opener reports Finished with the playhead pinned at 5.0 (V4.Cinematics.Playback.SequencerDrivenState). The module also emits the authoring contracts: five Sequencer templates (CampaignOpener, BossIntro, ARPGFinisher, Takedown, RivalryPostMatch), each with required story beats; two Movie Render Queue profiles (CoD campaign at 3840×2160 EXR, marketing trailer at 30 fps ProRes 4444); per-cell camera-language presets; and nine director commentary tracks gated on campaign completion via IsDirectorCommentaryUnlocked, which only unlocks when the completed-campaign list actually contains the campaign id (V4.Cinematics.Assets.TrackCameraAndTemplateValidation, V4.Cinematics.DirectorCommentaryExtended).

UV4SlowMoSubsystem is the showdown / death-cam time-dilation governor. ResolveBlendedDilation (V4SlowMoSubsystem.cpp:63) lerps from 1.0 down to the clamped target dilation over BlendInSeconds, holds for DurationSeconds, then lerps back to 1.0 over BlendOutSeconds; TickSlowMo advances the clock and auto-clears when the total elapses. The dilation is clamped to [0.05, 1.0] on activation. The honest seam: it produces GetEffectiveTimeDilation() for a caller to apply, but never calls UGameplayStatics::SetGlobalTimeDilation itself — the curve is real, the application is the integration point (V4.Cinematics.SlowMo.BlendLifecycle).

UI / HUD — V4UI#

MVVM: the one real engine-runtime binding#

UV4HUDViewModel is the module's genuine engine touch: it subclasses UMVVMViewModelBase and, in SetNumericBinding/SetTextBinding, dedupes (a no-op write returns false and broadcasts nothing) and otherwise calls BroadcastFieldValueChanged(FFieldId(BindingId, 0)) (V4HUDViewModel.cpp:92-95). That is the "all HUD bindings via MVVM; no direct attribute access from UMG" rule made concrete — a real ModelViewViewModel/FieldNotification change-notification, not a stub. UV4MVVMRegistry complements it with per-screen binding tables and BuildDefaultHUDBindings (V4MVVMRegistry.cpp:91), which encodes the six per-cell HUDs verbatim: Tactical-FPS (Health/Ammo/MiniMap/Killfeed), Stealth (LightGauge/SuspicionRadius/Disguise/Objective), Tactics (PartyPortraits/CommandQueue/Showdown), ARPG (HP/Stamina/Will/Posture/CharmSlots/LockOn), RTS (ResourceTicker/BuildMenu/MiniMap/GroupHotkeys), and Arcade (Score/Lives/WeaponIcon/BossHealth) (V4.UI.MVVMRegistry.Bindings).

Per-cell HUDs and the stealth bow-tie#

UV4StealthBowTieWidget is a real UUserWidget subclass whose static BuildPresentation (V4StealthBowTieWidget.cpp:15) maps an EV4LightExposureState (shared from V4Stealth) to a deterministic presentation: Dark → dim blue-grey tint, 0.2 wing-fill; Dim → amber tint, 0.55 wing-fill; Lit → bright tint, full wings, with bPulseAttention set when lux ≥ 1.0. This is the Splinter Cell light-gauge "bow-tie" read straight off the stealth gauge — logic and presentation data are real even though the visual WBP widget is not yet authored. UV4UIBootstrapSubsystem registers the front-end shell screens and drives focus navigation for gamepad-vs-mouse routing (V4.UI.Bootstrap.NavigationAndFocus).

Accessibility#

UV4AccessibilitySubsystem builds and validates the full launch accessibility contract into a UV4AccessibilitySettingsAsset. The substantive parts are real behavior, not toggles: ShouldApplyAimAssist (V4AccessibilitySubsystem.cpp:97) refuses aim assist in ranked PvP when the opt-out flag is set, and ResolveWukongParryWindowFrames widens the parry window by a configurable bonus — a base of 6 frames plus the default 4 yields 10 frames (:167, asserted exactly). ValidateAccessibilityLaunchCoverage enforces the floors: subtitle size > 1.0 with a speaker indicator, four colorblind profiles (Protanopia/Deuteranopia/Tritanopia/Monochrome) each covering HUD, minimap, and vision-cone overlays, six high-contrast HUDs (one per cell), audio-cue toggles, two one-handed schemes, and difficulty toggles that never silently disable achievements (V4.UI.Accessibility.LaunchCoverage). The V4/ui/launch-experience manifest layers the loading screens, What's-New modal, toast queue, and companion push on top.

How it connects (and where it does not yet)#

Presentation sits downstream of gameplay and reads, never writes. GAS verbs and combat events (./gas-layout.md) are what should call DispatchCue, DispatchBark, PlayCinematic, and ActivateSlowMo; the HUD view-models read attributes through MVVM and push nothing back. The poses these effects dress come from the animation pipeline — the MetaHuman facial-performance and lip-sync flags the animation module declares are realized here on the audio/VFX side — and the weather, fog, and debris cues are emitted into the streamed sandbox the world-streaming & procgen layer manages. The authored content these modules name — MetaSounds patches, Niagara systems, level sequences, compiled WBP widgets — is tracked as the *.v4asset.json descriptor set and belongs to the build-and-content path (./build-data-content.md).

The honest remaining work is uniform across all four modules and identical in shape to the animation layer's: the policy, math, and validation are real and tested; the authored .uasset and the final engine-API call (Niagara spawn, LevelSequence play, global time-dilation apply, MetaSound playback) are the pending integration. These are shippable units awaiting a call site and authored content, not stubs — every decision they make is computed, deterministic, and pinned to a known-correct value.