The Tier-1 client is the canonical V3 ("Lilith") runtime: one Unreal Engine 5.5
project at V3/ue/ that drives every premium surface — native desktop, console,
mobile, VR, and the browser (the same binary, server-rendered over Pixel
Streaming). It is the high-fidelity end of the
tiered client stack: Lumen GI, Nanite
geometry, MetaHuman-class avatars, and Sequencer-driven concerts can only run
inside UE5, so rather than fork a per-platform client, V3 ships a single project
that the build pipeline cooks eleven different ways and a Game Feature plugin
system that hot-loads tenants and event modes without an engine rebuild. The
shape of that project is not aspirational prose: V3/ue/V3.uproject pins
"EngineAssociation": "5.5" and "DisableEnginePluginsByDefault": true,
declares 17 C++ modules with explicit loading phases, and enables the exact
plugin set the design needs (PixelStreaming, GameplayAbilities, Niagara,
Metasound, Mover/PoseSearch, the Iris replication system,
OpenXR/OpenXRHandTracking, OnlineServicesEOS, Gauntlet). This page is the
module-, plugin-, and platform-level map of that client; the section hub is
../V3_ARCHITECTURE.md.
The reason the client is organised this way is operational. A metaverse that
must boot a cold editor in ≤ 25 s, cook for phones and an Apple Vision Pro from
one source tree, and load a "Saraswati concert" or a "Tara live class" as a
self-contained content bundle cannot be a monolith of always-on code. So V3
splits its concerns into narrow modules (each with its own *.Build.cs and a
small FV3…ModuleContract identity), declares everything tenant- or
event-specific as an ExplicitlyLoaded Game Feature plugin that ships in
the Registered state and only activates on join, and centralises the
platform-matrix knowledge in one validated registry of cook profiles in
V3Core. The same source even produces a second build target —
V3PixelStreamingWorker.Target.cs, a monolithic headless cook — so the browser
tier is literally this client running server-side. Everything below is grounded
in that tree.
What ships, honestly#
The module split, the platform cook-profile registry, the Game Feature plugin
pipeline, and the cross-language wire codec are real and tested. The 17
modules all build into both the V3 game target and the
V3PixelStreamingWorker target (V3/ue/Source/V3.Target.cs,
V3PixelStreamingWorker.Target.cs); V3Core is the
IMPLEMENT_PRIMARY_GAME_MODULE (V3/ue/Source/V3Core/Private/V3Core.cpp:852)
and carries a real eleven-platform cook-target registry whose per-platform
Validate() enforces RHI, shader-format, resolution, FPS, and XR-runtime rules;
the Game Feature generator and verifier are real editor commandlets; and V3Net
is a hand-written Protobuf varint/zig-zag codec that agrees byte-for-byte with
the TypeScript and Rust bindings of the same .proto. There are 32 automation
specs under V3/ue/Source/V3Tests/, and they assert specific values (e.g.
"Win64 shader format is SM6", "Quest 3 per-eye 90 fps budget"), not just
truthiness.
But the client is a logic-and-manifest skeleton, not a content-complete game. This is the part the monolith's prose overstates, and the honest position matters:
- Six of the 17 modules are module-contract stubs.
V3Gameplay,V3Animation,V3Cinematics,V3Persistence,V3Telemetry, andV3VFXare each ~49 lines — aDECLARE_LOG_CATEGORY, anFV3…ModuleContract, and anIMPLEMENT_MODULE. The monolith's "GAS abilities (interaction, asana lock, gesture, applause), attribute sets" forV3Gameplayis not implemented in C++; the only ability references in the repo are asset-path strings inside Game Feature manifests (e.g./Game/Saraswati/Abilities/AS_ConcertAudience), and those.uassetability sets are not committed. - There is no
UV3NetDriver, noUV3WorldSubsystem, and noV3NetTransportcxx-bridge. The monolith's "World-Server ↔ UE Network Integration" section describes aUNetDriversubclass and a local-mirror world subsystem; a grep ofV3/ue/Sourcefinds none of those symbols. What exists is the wire codec (V3Net) and a voice client model (V3Voice); wiring them to live actors is not in the tree. Treat that section as design intent. See Netcode Protocol and Physics. - Zero
.umapfiles are committed. Every cook profile targets/Game/World/Atrium/L_LilithCommonsAtrium, but that map exists only as a JSON scene descriptor (V3/ue/Content/World/Atrium/L_LilithCommonsAtrium.v3scene.json), not an authored level. The committed binary content is generated: 21GameFeatureData.uasset(one per plugin, machine-built from manifests), ~107 design-token Slate brush/style assets (built by a second commandlet), and oneConcertMaster.uassetSaraswati template.
So: the buildable engineering scaffold — modules, targets, cook profiles, plugin registration, the wire codec, the VR/avatar/UI subsystems with real logic — is genuinely here and under test. The authored game content — levels, ability blueprints, Niagara systems, MetaSound graphs, avatar meshes — is declared in manifests and scene descriptors but largely not committed. Where a module is a stub or a claim is unbacked, this page says so.
The 17-module split#
Each module owns one concern, builds with its own *.Build.cs, and exposes a
tiny FV3…ModuleContract (GetModuleName / GetOwnedSurfaceTag /
SupportsRuntimeLoad) so the split is introspectable. The runtime fifteen are
listed in V3.Target.cs ExtraModuleNames; V3Editor (Editor type) and
V3Tests (DeveloperTool) round out the seventeen in V3.uproject. The table
maps each module to what is actually in the tree today, not the aspirational
charter:
| Module | Files | State | What the code actually contains |
|---|---|---|---|
| V3Core | 9 | substantive | Primary game module, 120 fps bootstrap frame policy, 11-platform cook-profile registry, UV3GameFeatureData, AV3LilithGameMode/Pawn. |
| V3Gameplay | 3 | skeleton | Module contract only (~49 LOC). No GAS classes, abilities, or attribute sets in C++. |
| V3World | 5 | thin | Cold-join phase-budget fixtures, Atrium placeholder-scene validation, V3InWorldCameraProp. Not a replicated world subsystem. |
| V3Avatar | 12 | substantive | FV3MetaHumanRetargetTable (Oshun60 bindings), OpenXR hand-IK, V3HandIkSolver, V3PostureStateMachine, V3TaraEyesOpenPolicy. |
| V3Animation | 3 | skeleton | Module contract only (~49 LOC). |
| V3Input | 9 | substantive | V3HandGestureRegistry, V3VrFloorRecalibration, V3VrTimewarp (the [V3.OpenXR] runtime module). |
| V3Net | 4 | substantive | Hand-written Protobuf wire codec (FV3NetMultiplayerProtocolCodec); packet structs for snapshot-delta, presence, voice, interaction. |
| V3Voice | 4 | substantive | FV3VoiceRealtimeGatewayClient SFU model — Opus 24 kbps, DTLS-SRTP, Resonance spatialization, mouth-to-ear budget. |
| V3UI | 15 | substantive | V3DesignTokenLibrary, V3AvatarNameplateLod, V3ActivityState, VR comfort/calibration, the Tara physical-adjustment consent dialog. |
| V3Audio | 5 | partial | V3SteamAudioVr spatialization (~200 LOC) atop the module contract. |
| V3VFX | 3 | skeleton | Module contract only (~49 LOC). |
| V3Cinematics | 3 | skeleton | Module contract only (~49 LOC). |
| V3Persistence | 3 | skeleton | Module contract only (~49 LOC). |
| V3OnlineServices | 3 | substantive | EOS / V1-identity model packed into the module file (~403 LOC); covered by V3OnlineServicesIdentityTests. |
| V3Telemetry | 3 | skeleton | Module contract only (~49 LOC). |
| V3Editor | 11 | substantive | The Game Feature + design-token authoring commandlets and the token-reloader widget. |
| V3Tests | 35 | substantive | 32 automation specs: cook profiles, net codec, voice latency, OpenXR, avatar, VR comfort. |
The honest reading of this table: the client-side subsystems that have nothing to do with authored 3D content — networking codec, voice routing, VR comfort, avatar retarget math, design tokens, identity — are real and tested, while the modules that exist mainly to host authored gameplay/animation/VFX/cinematics content are still empty shells waiting on that content.
Module loading order and the primary game module#
V3.uproject assigns loading phases deliberately: V3Core and V3Gameplay
load PreDefault (so the cook policy and gameplay tags are up before world
actors), everything else loads Default, V3Editor is Editor-only, and
V3Tests is a DeveloperTool. V3Core's module is the project's
IMPLEMENT_PRIMARY_GAME_MODULE(FV3CoreModule, V3Core, "V3"). On startup it
applies a frame policy — t.MaxFPS = 120, bUseFixedFrameRate = false, and a
re-application on OnPostEngineInit so a late device-profile cannot clobber it
(V3Core.cpp:808–831) — matching DefaultEngine.ini's t.MaxFPS=120. The
default game mode is wired in config, not code: DefaultEngine.ini sets
GlobalDefaultGameMode=/Script/V3Core.V3LilithGameMode, and that
AV3LilithGameMode simply sets
DefaultPawnClass = AV3LilithPawn::StaticClass() with no HUD
(V3LilithGameMode.cpp).
The ≤ 25 s cold-start budget is enforced from two directions. V3.uproject sets
DisableEnginePluginsByDefault: true so nothing loads unless explicitly
enabled, and DefaultEngine.ini then carries a long +DisabledPlugins= block
(Android*, AppleARKit, Bridge, the DMX family, nDisplay,
VirtualProductionUtilities, WebBrowser*) plus a [V3.EditorPerformance]
section with ColdStartBudgetSeconds=25 and
DisablePluginListSource=V3.UE.ModuleSplit. The OpenXR posture is config-pinned
too: [V3.OpenXR] names RuntimeModule=V3Input and +RequiredBackends= Quest3
/ VisionPro / PSVR2 / ValveIndex / ViveFocus3.
The wire codec, concretely#
V3Net is the one place the Tier-1 client touches the world server, and it is a
genuine engineering artifact rather than a placeholder. V3NetProtocol.cpp
implements Protobuf wire format by hand — WriteVarint, ZigZagEncode32,
length-delimited keys — with no dependency on a protobuf runtime (the
V3Net.Build.cs deps are just Core/CoreUObject/Engine/GameplayTags/
V3Core). Transforms are packed for the bandwidth budget: position in
millimetres (FV3NetVector3Mm), rotation in milli-degrees
(FV3NetRotationMilliDegrees), expression intensity in basis points — which is
how the "≤ 32 bytes per avatar" target is met. The codec encodes and decodes
snapshot-delta, presence, voice-control, interaction, and client/server
envelopes, and BuildInEngineProtocolExchangeReport() produces the fixture that
V3NetProtocolTests checks against the canonical hex. That same .proto is
implemented three times (TS, Rust, this C++), and the cross-language golden-hex
parity test is the guard — see
Netcode Protocol and Physics and
World Server and Gateway.
Game Feature Plugins: tenants and event modes#
Everything tenant- or event-specific is shipped as a Game Feature plugin so
it can be hot-loaded on join, unloaded on leave, and included or excluded per
platform/region at cook time without an engine rebuild. There are 21 plugins
under V3/ue/Plugins/GameFeatures/ — 3 tenants and 18 event modes:
| Kind | Plugins |
|---|---|
| Tenant | V3Tenant_TaraStudio, V3Tenant_SaraswatiStage, V3Tenant_LilithCommons |
| Tara | V3Mode_TaraLiveClass, V3Mode_TaraOnDemand, V3Mode_TaraPrivate, V3Mode_TaraCohort |
| Saraswati | V3Mode_SaraswatiConcert, V3Mode_SaraswatiClub, V3Mode_SaraswatiListening, V3Mode_SaraswatiDrop, V3Mode_SaraswatiFestival |
| Commons | V3Mode_CommonsAtrium, V3Mode_CommonsObservatory, V3Mode_CommonsDebateHall, V3Mode_CommonsLectureHall, V3Mode_CommonsStacks, V3Mode_CommonsRitualRoom, V3Mode_CommonsAretAtrium, V3Mode_CommonsLanternHall, V3Mode_CommonsSolitaryCell |
Every *.uplugin carries the same three flags that make hot-loading work:
"EnabledByDefault": false, "ExplicitlyLoaded": true, and
"BuiltInInitialFeatureState": "Registered" (verified across the tenant and
mode plugins). Registered means the plugin's assets are discoverable but inert
until the Game Features subsystem transitions it to Active; ExplicitlyLoaded
keeps it out of the default mount so cold start stays cheap. Each plugin also
declares "Plugins": [GameFeatures, ModularGameplay] as dependencies.
The manifest → asset → runtime pipeline#
The interesting part is that the registration data is authored as JSON and
compiled into a UAsset by a commandlet, so the source of truth is reviewable
text and the binary is reproducible. Each plugin ships a
Content/<Plugin>/GameFeatureData_<Plugin>.v3asset.json manifest. For
V3Mode_SaraswatiConcert it reads:
{
"assetType": "GameFeatureData",
"plugin": "V3Mode_SaraswatiConcert",
"activation": "join-saraswati-concert",
"gameplayTags": [
"Mode.Saraswati.Concert",
"Saraswati.Sequencer",
"Audience.BandedSeating"
],
"abilitySets": [
"/Game/Saraswati/Abilities/AS_ConcertAudience",
"/Game/Saraswati/Abilities/AS_ConcertOperator"
],
"worldTemplates": [
"/Game/Venues/Saraswati/Concert/LVL_SaraswatiConcert_Template"
],
"niagaraSystems": [
"/Game/VFX/Saraswati/NS_ConcertStage",
"/Game/VFX/Saraswati/NS_AudienceLightWave"
],
"metaSounds": [
"/Game/Audio/Saraswati/MS_ConcertMainMix",
"/Game/Audio/Saraswati/MS_AudienceBandMix"
]
}
The target of that compilation is UV3GameFeatureData
(V3/ue/Source/V3Core/Public/V3GameFeatureData.h), a UGameFeatureData
subclass with seven EditDefaultsOnly fields — FeatureName,
ActivationPolicy, and arrays of gameplay tags, ability-set ids, world-template
ids, Niagara-system ids, and MetaSound ids. Its
HasCompleteRegistrationManifest() refuses an incomplete manifest
field-by-field ("Game Feature manifest is missing required field: {0}"), and
under WITH_EDITOR that check is wired into IsDataValid() so a malformed Game
Feature fails Data Validation rather than shipping inert.
V3GenerateGameFeatureDataCommandlet::Main (V3/ue/Source/V3Editor/Private/)
walks every *.uplugin under Plugins/GameFeatures, reads and strictly
validates the JSON (every array must be non-empty; assetType must equal
GameFeatureData; the manifest's plugin must match the descriptor filename),
writes a <Plugin>Tags.ini of GameplayTagList= entries, then idempotently
creates-or-updates the GameFeatureData.uasset via SavePackage — re-running
is a no-op when AssetMatchesManifest holds. That is why all 21 plugins
have a committed Tags.ini and a committed GameFeatureData.uasset that
exactly mirror their manifests.
The companion V3VerifyGameFeaturesCommandlet proves the plugins actually
hot-load. For each descriptor it drives the real
UGameFeaturesSubsystem::ChangeGameFeatureTargetState to Active, ticks the
editor while polling for completion (10 s timeout), asserts
IsGameFeaturePluginActive, then transitions back to Registered — a genuine
load/unload round-trip per plugin, not a flag check. Caveat: because the
manifests point at /Game/... abilities, levels, Niagara, and MetaSounds that
are not committed, this verifier exercises the registration and
activation-state machinery, not a fully-populated feature.
Platform cook target profiles#
The platform matrix lives in one validated registry in V3Core:
FV3CookTargetProfileRegistry (V3Core.h / V3Core.cpp) exposes a Build…
factory for each of eleven EV3CookPlatform values and a
BuildCookRunCommand that renders the profile's argument vector into a
RunUAT BuildCookRun … string. Each profile is a FV3CookTargetProfile whose
Validate(OutReason) is a real gate, and the rules are platform-aware rather
than generic:
| Profile (factory) | RHI / shader | Target res / FPS | Reference HW | Renderer rule enforced by Validate() |
|---|---|---|---|---|
BuildWin64LumenNaniteProfile |
DX12 / PCD3D_SM6 | 2560×1440 @ 60 | RTX 3060 | Lumen + Nanite + VSM required; HW ray tracing on. |
BuildMacAppleSiliconProfile |
Metal / SF_METAL_SM6 | 2560×1440 @ 60 | M2 Pro | Lumen + Nanite + VSM; HW RT off; arm64. |
BuildLinuxLumenNaniteProfile |
Vulkan / SF_VULKAN_SM6 | 2560×1440 @ 60 | RTX 3060 | Desktop renderer; x64. |
BuildSteamDeckMobileProfile |
Vulkan / SF_VULKAN_SM5 | 1280×720 @ 40 | Steam Deck LCD | Desktop renderer features must be off; 720p/40 fps floor. |
BuildIosForwardPlusProfile |
Metal / SF_METAL | 2556×1179 @ 60 | iPhone 15 Pro | Mobile shaders; Lumen/Nanite/VSM off. |
BuildAndroidVulkanForwardPlusProfile |
Vulkan / SF_VULKAN_ES31_ANDROID | 2992×1344 @ 60 | Pixel 8 Pro | Mobile Vulkan ES3.1; ASTC cook flavor. |
BuildQuest3OpenXrProfile |
Vulkan / SF_VULKAN_ES31_ANDROID | 2064×2208 @ 90 (per eye) | Quest 3 dev kit | Android Vulkan + XR runtime profile (hand tracking, foveation, passthrough). |
BuildVisionProOpenXrProfile |
Metal / SF_METAL | 3660×3200 @ 90 (per eye) | Vision Pro simulator | visionOS Metal + XR profile (hand + eye gaze, foveation). |
BuildPsvr2OpenXrProfile |
GNM / SF_PS5 | 2000×2040 @ 90 (per eye) | PSVR 2 dev kit | Nanite on, Lumen/VSM/RT off; XR profile (eye gaze, foveation). |
BuildPs5LumenNaniteProfile |
GNM / SF_PS5 | 3840×2160 @ 60 | PS5 dev kit | 4K60; HW RT off; must carry a PSVR2 90 fps secondary target. |
BuildXboxSeriesXLumenNaniteProfile |
D3D12 / SF_XSX | 3840×2160 @ 60 | Xbox Series X dev kit | 4K60; HW RT off for the post-GA budget. |
The validator is not cosmetic. A desktop profile that drops Lumen, Nanite, or
virtual shadow maps fails; a "constrained mobile" profile (Steam Deck, iOS,
Android, Quest 3, Vision Pro) that enables any desktop-only feature fails; an
XR profile that targets < 90 fps, omits per-eye dimensions, or claims a required
capability without the matching input profile fails via
FV3CookXrRuntimeProfile::Validate (e.g. "XR runtime profile must drive avatar
hand IK", "must enable foveated rendering"). Every profile must cook the Atrium
map, declare archive + manifest paths, and supply a BuildCookRunArgs vector of
at least eight tokens. These rules are the subject of one automation spec per
platform under V3/ue/Source/V3Tests/ (e.g. V3Win64CookProfileTests.cpp
asserting Rendering.ShaderFormat == "PCD3D_SM6", render dimensions, and that
the rendered command contains -platform=Win64 and the Atrium -map=).
The whole project is built with deterministic floating point — V3.Target.cs
appends /fp:strict /fp:except- on Win64 and -fno-fast-math -ffp-contract=off
elsewhere, with bUseUnityBuild = false and bBuildDeveloperTools = false,
plus ProjectDefinitions V3_LILITH=1 and V3_ENGINE_UE55=1.
The Pixel Streaming worker is the same client, server-side#
The browser tier is not a separate codebase: V3PixelStreamingWorker.Target.cs
compiles the identical fifteen runtime modules as a monolithic, headless,
shipping-with-logging target (LinkType = Monolithic,
bBuildWithEditorOnlyData = false, bUseLoggingInShipping = true) and adds the
defines V3_PIXEL_STREAMING_WORKER=1, V3_HEADLESS_PIXEL_STREAMING=1, and (on
Win64) V3_PIXEL_STREAMING_WIN_WORKER=1. A GPU worker runs this build, renders
frames, and streams them over WebRTC to a browser that sends input back — so a
browser attendee is, from the world server's perspective, just another Tier-1
client. How sessions are matched to workers and POPs, and how that tier is
chosen, belongs to
Tier Routing and Pixel Streaming; the
point here is that the client binary is shared, and the only delta is a target
file plus four cook profiles' worth of headless flags.
How the client connects to its neighbours#
- To the world server / gateway.
V3Net's codec speaks the@oshun/multiplayer-protocolwire format (the canonical.protolives inlibs/v3/multiplayer-protocol/); the Rust world server and gateway are the authority. See World Server and Gateway and Netcode Protocol and Physics. - Voice.
V3Voice'sFV3VoiceRealtimeGatewayClientmodels a LiveKit-compatible SFU atwss://lilith-realtime-gateway.internal/v3/realtime/sfu— Opus 24 kbps mono, DTLS-SRTP with replay protection, an 80 ms mouth-to-ear budget, and Resonance-Audio spatialization gated behind an honestIsResonanceAudioRuntimeAvailable()capability check. Detail lives in Avatar, Animation, and Audio. - Identity.
AV3LilithPawncarries the V1 bridge at the actor level:ApplyV1IdentityNameplaterunsSanitizeV1DisplayNameForNameplate(trim, collapse whitespace, 32-char clamp,"Guest"fallback) and flipsbBoundToV1Identity. The TypeScript side is@oshun/lilith-identity-bridge(libs/v3/lilith-identity-bridge/). See V1 Integration and Identity Bridge. - Avatars and UI.
V3Avatarholds the Oshun60 MetaHuman retarget table and posture/hand-IK/eyes-open logic (note its deps are engine-only — the retarget is a binding-and-validation model, not a live MetaHumanRuntime rig);V3UIcarries the design-token Slate library (built into the ~107 brush/style.uassetbyV3GenerateDesignTokenAssetsCommandlet), avatar-nameplate LOD, VR comfort/calibration, and the Tara physical-adjustment consent dialog. These feed Tara Classes, Aja, and Commons and the Saraswati Stage Pipeline. - Contracts. The launch-decision, tenant, and event-mode contracts the
client honours are typed in
libs/contracts/src/v3/(lilith.ts,saraswati.ts,commons.ts,tara.ts) — the same registry the glossary and layout catalogues.
Related#
- Product Promise and Architecture and Subsystem Glossary and Layout — the why and the where of the tiered stack
- Tier Routing and Pixel Streaming and Tier-2 Fallback Web Client — the other tiers
- World Server and Gateway, Netcode Protocol and Physics — the authority and wire this client speaks to
- Avatar, Animation, and Audio, Saraswati Stage Pipeline, Tara Classes, Aja, and Commons, Authoring and Content Pipeline — what the Game Feature plugins load
- Data, Tenancy, and Residency, V1 Integration and Identity Bridge, Persona Policy, Provenance, and Rights, Commerce and Royalties, Observability, Performance, Security, and Launch
- The section hub: ../V3_ARCHITECTURE.md