V2's interface layer is the everything-the-player-touches surface: front-end
menus, the in-match HUD, the settings tree, and the accessibility and VR/AR
support that sit on top of them. It serves every player — but it is engineered
first for the ones who need it most, because console accessibility is
cert-critical (Sony, Microsoft, and Nintendo each gate shipping on a
required-feature list). What makes V2's interface unusual is how it is built:
not as a pile of opaque Blueprint widgets, but as plain-C++ value types that
validate themselves, paired with JSON contracts and Python gates. The
canonical example is FV2UIAccessibilitySettings
(V2/ue/Source/V2UI/Public/V2UITypes.h:9135), an eleven-field struct with a
Normalize() method and an IsValidSettings() self-check that every HUD widget
and accessibility subsystem reads from. The whole V2UI module is 85
implementation files (V2/ue/Source/V2UI/Private/*.cpp), and a single
3,894-line automation test, V2.UI.Module (FV2UIModuleSpec in
V2/ue/Source/V2Tests/Automation/UI.Module.spec.cpp), exercises them with
roughly 1,500 TestTrue/TestEqual assertions — HUD-scale clamping,
camera-shake normalization, reduced-motion variants, the screen-reader profile,
colour-blind palettes, and caption configuration all run headless, with no live
match.
Why this shape? Because a HUD that binds directly to the Ability System Component cannot be tested without spinning up a match, and an accessibility claim that cannot be proven offline cannot be presented to a platform cert reviewer. By moving the decisions (what colour, what scale, how much shake) into deterministic structs and the enforcement into validators and gates, V2 makes presentation provable and — crucially — keeps every accommodation on the correct side of the rollback boundary. This page is a companion to the architecture catalogue; the hub is ../V2_ARCHITECTURE.md.
What ships, honestly#
Real and tested. The accessibility subsystems are genuine C++ with
domain-specific algorithms and constants, each with a BuildDefault* factory, a
self-validating profile, and assertions in the module spec: the colour-blind
palette (V2ColorblindSafePalette.cpp, 17 indicator roles × 5 modes), the
photosensitivity filter (V2PhotosensitivityFilter.cpp, a 3-flash-per-second
cap), visual sound indicators (V2VisualSoundIndicators.cpp, directional
off-screen cues with real screen-edge projection), the camera-shake intensity
slider (V2CameraShakeIntensitySlider.cpp), the screen-reader bridge
(V2ScreenReaderBridge.cpp), reduced motion, high contrast, font options,
subtitle rendering, and more. The TypeScript services @v2/iris-accessibility
and @v2/psyche-caption-streaming
(apps/v2/{iris-accessibility,psyche-caption-streaming}/src/) are real and
compose the shared @iris/accessibility and @psyche/caption-streaming
libraries. VR/AR ships as a validated data + contract layer
(V2/balance/data/vr-ar-support.json plus
V2/ue/Content/V2/UI/VRARSupport_V2_Contract.json) with a CI gate. A family of
61 Python checkers (V2/ue/Tools/check-v2-* covering accessibility, HUD,
caption, VR, colour-blind, photosensitivity, …) gate these surfaces in CI.
Spec-only / aspirational. The architecture monolith describes a CommonUI 5.5
activation tree under Content/UI/FrontEnd/ with W_HUD_Base, W_Settings_*
panels, an IRulesetHUD/IModeHUD injection interface, and .uplugin
HUDInjections. Those binary UMG assets do not exist in the tree. What
ships under V2/ue/Content/V2/UI/ is JSON contracts (for example
RulesetHUDOverlay_V2_Contract.json,
HUDAccessibilityPresentation_V2_Contract.json), and the per-ruleset HUD is
realised as the spec-driven C++ class UV2RulesetHUDOverlayWidget, not a
literal UInterface. Treat the front-end .uasset activation tree, the HUD
render budgets (≤0.6 ms/frame), and the named linters
check-widget-tree-depth.py / check-a11y-coverage.py (neither is present) as
design intent. The monolith's Source/V2Peripherals/ module does not exist
either — peripheral/adaptive-controller logic lives in V2Input and
V2/balance/data/hardware-peripherals.json.
Provider / platform-gated. Live OS screen-reader announcement (NVDA, JAWS, VoiceOver, TalkBack) is reached through a real, validated tree-export model, but the actual OS-runtime hookup is the integration seam. Per-platform VR rendering paths (PSVR2 foveation/eye-tracking, Quest async-timewarp, Vision Pro passthrough) are described in data and depend on platform SDKs not present in this tree. Platform accessibility certification and VR comfort review are external sign-offs.
The settings model: one struct, normalized, validated#
Everything downstream reads FV2UIAccessibilitySettings. Its fields and bounds
are the contract:
| Field | Type | Range / default |
|---|---|---|
HUDScalePercent |
int32 | 75–150, default 100 |
HUDOpacity |
float | 0.0–1.0, default 1.0 |
bReducedMotion |
bool | default false |
CameraShakeIntensityPercent |
int32 | 0–100, default 100 |
bScreenReaderAnnouncements |
bool | default true |
bLargeText / TextScale |
bool / float | TextScale 0.5–3.0 |
PaletteMode |
EV2UIColorPaletteMode |
Default · Protanope · Deuteranope · Tritanope · Monochromacy |
ContrastMode |
EV2UIContrastMode |
Standard · Medium · High |
ReadableFontMode |
EV2UIReadableFontMode |
Default · AtkinsonHyperlegible · OpenDyslexic |
LetterSpacingPercent |
int32 | 0–50 |
Normalize() snaps every field into range, and the module spec proves it:
setting HUDScalePercent = 153 and CameraShakeIntensityPercent = 42 yields a
clamped 150 and a 40 that rounds to the nearest five
(UI.Module.spec.cpp:3589–3603), with GetCameraShakeIntensityMultiplier()
resolving to 0.40. The three enums are exact: the palette modes line up
one-for-one with the colour-blind catalogue, and the readable-font modes name
the two bundled accessibility typefaces (Atkinson Hyperlegible, OpenDyslexic).
Two sibling configs extend the model — FV2AccessibilityVisualConfig
(Accessibility.Visual.V2, with MaxFlashesPerSecond, high-contrast HUD,
photosensitivity-limiter, and opponent/hit-zone outlining flags) and
FV2AccessibilitySubtitleCaptionConfig (Accessibility.Captions.V2, with sign-
language locales, caption cues, subtitle background opacity, and speaker tags).
The settings screen that edits this lives in V2SettingsScreen.cpp. It is a
spec-validated category tree (UV2SettingsScreen::ConfigureFromSettingsSpec),
and its predicate methods are what the gates assert against:
HasCameraShakeIntensitySlider() checks the row is a 0–100/step-5/default-100
slider; HasReducedMotionModeToggle() checks the description literally mentions
"screen shake," "particle effects," and "camera transitions";
HasFontAccessibilityOptions() checks the readable-font choice exposes Default
/ AtkinsonHyperlegible / OpenDyslexic alongside a 0–50 letter-spacing slider;
and HasOneHandedControls() / HasControlsRebind() cover motor remapping.
Privacy rows require confirmation (HasPrivacySafeguards()), and locale
selection covers OS-auto-detect plus an explicit text/voice language split.
The determinism boundary — the load-bearing idea#
The single most important architectural rule here: accessibility accommodations are presentation-only and must never alter the deterministic simulation. V2 is a rollback-netcode fighting game (see ./rollback-netcode-and-tag-team.md and ./combat-system-gas-frame-data-and-determinism.md), so a visual setting that changed even one frame of hit-stop would desync a match. The code enforces the wall explicitly.
The camera-shake slider is the clearest proof. ResolveShakeIntensity() takes a
normalized settings struct, an authored intensity, and a source (CombatHit,
EnvironmentalImpact, VehicleImpact, Cinematic, UIImpact), and returns
ResolvedIntensity = AuthoredIntensity × multiplier. The validator
FV2CameraShakeIntensityResolution::IsValidResolution() then refuses any result
where ResolvedIntensity > AuthoredIntensity (it may only attenuate, never
amplify), where 0% fails to fully disable shake, or where the resolution is
not bGameplayDeterministic and bDoesNotAlterFrameData. The profile's
bDoesNotAffectHitStopFrameData flag feeds those booleans, so the type system
records that the slider scales the camera but leaves hit-stop frame data
untouched. The slider is also marked bTournamentSafe.
The same principle governs EV2AccessibilityRankedEligibility (RankedAllowed /
RankedDisabled). Visual-only accommodations — reduced motion, camera shake,
colour-blind palette, high contrast — do not flip ranked eligibility. Motor
assist macros that change gameplay outcomes (auto-combo, motion-shortcut,
parry-window-extend, block-assist) do flip it, routing the player into
assist-eligible queues. The interface layer therefore carries the policy that
separates "I changed how the game looks" from "I changed how the game plays."
In-match HUD and per-ruleset injection#
The HUD base class is UV2HUDWidget (declared in V2ActivatableScreen.h);
every overlay derives from it. Per-ruleset meters are injected through
UV2RulesetHUDOverlayWidget::ConfigureFromRulesetHUDOverlaySpec(), which takes
a FV2RulesetHUDOverlaySpec keyed by EV2ShaktiRulesetId (the Shakti
fighting-ruleset identity — see
./combat-system-gas-frame-data-and-determinism.md)
and a list of FV2RulesetHUDMeterSpec/EV2RulesetHUDMeterKind entries. The
widget exposes HasMeterKind(), IsReducedMotionSafe(), and
IsSpectatorSafe() — the last two are how a ruleset overlay proves it honours
the reduced-motion accommodation and is safe to show in spectator/broadcast
contexts. The module spec asserts RulesetHUDWidget->IsReducedMotionSafe()
directly (UI.Module.spec.cpp:646).
A worked example is the MMA/UFC body-damage overlay.
UV2MMABodyDamageIndicatorOverlay (a UV2HUDWidget) is configured from
FV2MMABodyDamageIndicatorSpec; on a valid spec it calls
ConfigureFromHUDEntry(OverlaySpec.ToHUDWidgetEntry()) and then surfaces
GetVisualZoneTag(), GetVisualIntensity(), IsLimbBruisingVisible(), and
IsBodyRednessVisible(). The overlay refuses to configure from an invalid spec
(ConfigureFromOverlaySpec returns false and resets), which is the fail-loud
pattern repeated across the module — a HUD widget either holds a validated spec
or holds nothing.
HUD scaling and reduced motion flow through UV2UISubsystem (the LocalPlayer
subsystem): SetHUDScalePercent(70) clamps to 75,
SetHUDReducedMotionEnabled(true) returns a presentation with
bReducedMotionVariantActive, and SetHUDHighContrastEnabled(false) toggles
the high-contrast theme (UI.Module.spec.cpp:3442–3516). The HUD-scale slider
is documented as 75–150 % in 5 % steps with an 18 px @ 1080p
minimum-readable-size floor; the clamping is real and tested, while the
design-time min-readable linter named in the monolith is not a shipped file.
Visual accessibility#
Colour-blind-safe palette. FV2ColorblindSafePalette::BuildDefaultCatalog()
ships 17 indicator roles — Health, Guard, Stamina, Super, Warning, Danger,
Success, Objective, QuestPrimary, QuestOptional, Interact, Focus, three Ping
qualities, TeamAlly, TeamEnemy — and each role carries five mode-specific
colours (Default plus four vision types) and a non-colour
NonColorShapeTokenId and TextFallbackTokenId. Validation is strict: every
colour is finite/normalized RGBA, the catalogue must declare
bNoColorOnlyState, every entry must meet a MinimumContrastRatio ≥ 4.5:1
(WCAG AA), and ProvidesModeSpecificAlternatives rejects any entry whose
protan/deutan/tritan/achromatic colour merely repeats the default. The actual
RGB values are the recognised Wong colour-blind-safe set (the 0.00, 0.45, 0.70
blue and 0.90, 0.62, 0.00 orange recur deliberately across roles). Because
each role also has a shape and a text token, no V2 state is ever encoded by
colour alone — the architecture's "widgets pull semantic colour, never raw hex"
rule made structural.
Photosensitivity filter. FV2PhotosensitivityFilter enforces a hard cap of
3 flashes per second — the three-flash threshold from WCAG 2.3.1 / the
Harding test — with per-target rules for RapidFlashing, StrobeEffects,
HighContrastFlicker, LightningVFX, and the KO X-Ray. Each rule derives
MinFlashIntervalSeconds = 1 / MaxFlashesPerSecond and is rejected if it
exceeds 3 fps or drops below a 0.33 s interval. ApplyFilter() clamps an
authored flash rate down to the cap and scales luminance/contrast deltas by
per-target factors (e.g. strobe luminance × 0.25); the profile's own validator
runs a self-test that an authored 12 fps strobe is filtered to ≤3 fps. The
filter bAppliesBeforePostProcess and bRequiresContentReviewTag, and stays
bGameplayDeterministic.
Reduced motion, high contrast, fonts, text scaling. Each is a real subsystem
with the same profile pattern: V2ReducedMotionMode.cpp
(Accessibility.ReducedMotionMode.V2, required-target coverage),
V2HighContrastMode.cpp (459 lines), V2FontAccessibilityOptions.cpp (438
lines, the Atkinson/OpenDyslexic + letter-spacing model), V2TextScaling.cpp,
V2ScreenMagnification.cpp, and V2ColorblindSimulation.cpp (a developer-side
simulation shader for authoring review). Reduced motion is a visual-only
accommodation: the architecture is explicit that enabling it does not disable
ranked play.
Auditory accessibility#
Visual sound indicators. FV2VisualSoundIndicators turns off-screen audio
into on-screen direction. Three cue types map to fixed shapes — Gunfire →
Chevron, Footstep → Footprint, EnvironmentalHazard → WarningDiamond — and the
geometry is real: ProjectDirectionToScreenEdge() normalizes a relative
direction, scales it to the safe-area edge (0.5 − padding), and clamps it into
the screen; BuildRenderItem() then sets rotation from atan2, scales the icon
with loudness and proximity (0.75 + loudness·0.35 + (1−distanceRatio)·0.20,
clamped 0.75–1.30), and fades opacity with distance. Every rule must set
bWorksWhenAudioMuted, bNonColorShapeCoding, a distance ring, and a threat
level; pulse rate is bounded 0.5–6 Hz and the profile shows at most 6
simultaneous indicators. This is the "audio cue → visual cue" promise made
concrete and deterministic.
Caption streaming. buildV2PsycheCaptionStreamingSurface()
(apps/v2/psyche-caption-streaming/src/psyche-caption-streaming.ts) plans a
real-time caption session over five surfaces — in-match-hud,
spectator-overlay, replay-viewer, companion-second-screen,
broadcast-observer — each with its own render target, position, alignment,
priority, and line cap (SURFACE_RENDERING). It builds one channel per (surface
× target language), capped at the underlying engine's maxChannels, registers
speakers for attribution, and emits a V2PsycheCaptionSessionPlan. The
netcode-safety guarantees are explicit fields: offRollback: true,
mayInfluenceRollback: false, and deterministicImpact: 'none' with the policy
string off-rollback-caption-ui-only — captions read presentation state and
never the deterministic input stream. Validation is fail-loud rather than
silent: buildReasonCodes() returns a non-empty list (which the cert gate
treats as a block) when matchId is missing, no target language is supplied,
accessibility mode is disabled, the in-match-HUD surface is absent, or no
speaker is rostered.
@v2/psyche-caption-streaming composes the shared @psyche/caption-streaming
runtime (libs/psyche/caption-streaming); audio-event duplication for
hearing-impaired play is handled in V2AudioFeedbackDuplication.cpp, and the
closed-caption authoring workflow in V2ClosedCaptionAuthoringTool.cpp.
Screen-reader bridge and the Iris service#
FV2ScreenReaderBridge::BuildDefaultTree() builds an exportable accessibility
tree (Accessibility.ScreenReaderBridge.V2): a root Window node, a MainMenu
list with Play and "Accessibility settings" buttons, a QuestTracker list whose
active objective is a live region, a Notifications live region, and help text.
Nodes carry stable ids, roles, labels, hints, focus order, and action metadata,
and the validator enforces structural integrity — a root must be a Window,
every non-root node must reference a registered parent, and the tree must cover
the required roles (Window, Button, Text, List, ListItem, LiveRegion). Platform
bindings are declared for three targets: UIAutomation (the Windows API
NVDA and JAWS consume), NSAccessibility (macOS/iOS VoiceOver), and TalkBack
(Android). It also converts quest objectives into nodes
(BuildNodeFromQuestDescriptor) so the open-world quest UI is screen-reader
addressable (see
./open-world-coop-and-special-modes.md).
This is an honest model-and-binding layer: the tree, roles, and focus order are
real and validated; the actual OS-runtime announcement is the platform seam the
bindings target.
The TypeScript service @v2/iris-accessibility
(buildV2IrisAccessibilitySurface) is the cross-client owner. It composes
@iris/accessibility (dyslexia-friendly typography, cognitive-load reduction,
screen-reader bridge, native-UE hooks) and the caption engine from
@psyche/caption-streaming, and returns a surface that declares
controlsScreenReaderBridge, controlsDyslexiaCognitiveAssistiveUi,
companionAndMainClientBridge, nativeUeAccessibilityHooks,
certGateBlocksRelease: true, and mayInfluenceRollback: false. Its event
topics include v2.accessibility.cert-gate.failed — accessibility failures are
first-class events, and the cert gate is wired to block release. Both services
re-export through
apps/v2/{iris-accessibility,psyche-caption-streaming}/src/index.ts.
VR / AR#
VR/AR ships as a validated catalogue, not a runtime module — there is no V2VR
C++ module in V2/ue/Source. The source of truth is
V2/balance/data/vr-ar-support.json (schema v2.ui.vr-ar-support-data.v1),
mirrored by V2/ue/Content/V2/UI/VRARSupport_V2_Contract.json and validated by
V2/ue/Tools/check-v2-vr-ar-support.py (plus check-v2-vr-camera-rig.py). The
data describes:
- Platforms. PSVR2 (4K-per-eye, 120 Hz, foveated rendering, eye tracking, DualSense Edge), Meta Quest 3 (native VR, hand tracking, per-frame async-timewarp, cross-buy with PC), Apple Vision Pro (mixed reality, passthrough, gaze-and-pinch, spatial Atmos audio), and PC VR (SteamVR / Oculus Rift S / Windows MR with per-platform render scale).
- Comfort. Three comfort levels, turning vignette, snap-turn, per-player comfort selection, minimal HUD, virtual gauge glances, and physical 3D UI objects — the VR analogue of the flat HUD.
- Mixed reality. Vehicles that drive on the real-world floor, fighting in a real room, an AR spectator companion that can track a friend's match on a TV or phone screen.
- Cross-play. Non-ranked opt-in VR-vs-non-VR, with ranked separating VR pools (the input-fairness rule that also governs wheel-vs-gamepad — see ./online-backbone-and-competitive-integrity.md).
- Certification. Per-platform comfort review plus epilepsy and motion-sickness compliance for Sony VR TRC, the Meta Quest Store, and the Apple Vision Pro App Store.
- VR accessibility. Seated mode, a dedicated wheelchair-seated mode, one-controller mode, voice-activated menus, and customizable 3D-space subtitle positioning.
The ruleset-compatibility block marks racing and (toggleable) fighting as
VR-eligible with motion-sickness considerations and per-family camera modes,
which is how VR connects to
./racing-and-vehicle-architecture.md.
The gate cross-checks the contract, the balance data, an engineering doc, and
the CI workflow, and requires owner modules
{V2UI, V2OnlineServices, V2Audio, V2Racing, V2Combat} — so even though there
is no VR runtime in this tree, the support matrix is enforced rather than
asserted.
Testing and gates#
Two layers verify the interface. The UE automation test V2.UI.Module runs
headless in the editor context and asserts behaviour, not existence — scale
clamping resolves to specific integers, camera-shake 42 → 40 with multiplier
0.40, reduced-motion produces an active variant, the HUD accessibility profile
binds the screen-reader tree id Accessibility.ScreenReaderBridge.V2 and the
HUD.Theme.HighContrast theme, and the caption/subtitle config validates across
subtitles, closed captions, sign language, audio description, and opacity. The
second layer is the 61-strong check-v2-* Python gate family (e.g.
check-v2-colorblind-safe-palette.py, check-v2-photosensitivity-filter.py,
check-v2-psyche-caption-streaming.py, check-v2-iris-accessibility.py,
check-v2-vr-ar-support.py), which cross-reference the C++ owners, the JSON
contracts, the balance data, and the docs. For how these gates fold into the
release pipeline, see
./telemetry-performance-testing-and-release-gates.md;
for the cert-blocking compliance view, see
./security-compliance-and-sister-monorepo-integration.md.
Related#
- ./v2-product-promise.md — what V2 promises players, and where accessibility sits in that promise
- ./glossary-and-module-topology.md — the
V2UImodule among the rest of the engine modules - ./combat-system-gas-frame-data-and-determinism.md and ./rollback-netcode-and-tag-team.md — the deterministic simulation the interface layer must never disturb
- ./animation-and-input-pipeline.md — input rebinding, adaptive controllers, and the IMC the input-diagram overlay reads
- ./game-modes-training-and-replay.md and ./presentation-av-and-signature-content.md — tutorial overlays, replay/spectator captions, and signature presentation
- ./racing-and-vehicle-architecture.md and ./open-world-coop-and-special-modes.md — VR-eligible rulesets and the quest UI the screen-reader bridge addresses
- ./online-backbone-and-competitive-integrity.md — VR/assist pool separation and ranked eligibility
- ./esports-companion-and-ai-services.md, ./live-ops-store-progression-and-community.md, and ./build-cook-assets-data-and-production.md — companion-app captions, the notification centre, and how UI contracts are cooked
- ./telemetry-performance-testing-and-release-gates.md and ./security-compliance-and-sister-monorepo-integration.md — the gate family and accessibility certification
- The section hub: ../V2_ARCHITECTURE.md