Open-World Narrative · Architecture

Live Service & DLC

A focused page within the Open-World Narrative Architecture documentation. The full map and every sibling page live in the Architecture hub.

5sections11 minread1diagram

On this page

V5 is an open-world narrative universe split into cells — an Urban heist city, a three-era Period crime saga, a Frontier outlaw trail, a Hunter's monster-contract world, a Sci-Fi galactic theatre, and the cross-cutting Mind Palace deduction layer — and a universe that big is a service, not a box. It ships five launch cells and then keeps growing for years: a 90-day season every quarter, a 100-tier battle pass, weekly cold-case packs, persistent world events, a whole Steampunk-Detective DLC cell, and a Year-3 roadmap that adds four sub-cells and a new console SKU. Two pieces of machinery make that post-launch life real rather than promised. The first is the live-service calendar service — a NestJS package under apps/v5 that publishes seasonal rotations, world events, and hot-fix windows over JWT-gated, ETag-cacheable feeds, mirrored by a family of UV5_Online_* Blueprint builders in the UE client. The second is the DLC boundary: every cell and mode is a GameFeature plugin discovered, classified, and build-target-gated by V5Modes, so a new cell drops in as a content-bearing plugin without forking the engine. This page explains how both are wired, what is real TypeScript and C++ on disk versus authored-but-uncooked content, and where the shipped code is honestly thinner than the architecture prose. The section hub is ../V5_ARCHITECTURE.md.

The design choice underneath everything is the same one the rest of V5 follows, and it is the same competitive line V2 and V4 drew: nothing sold or seasonal ever touches gameplay power. Every calendar payload asserts exclusiveGameplayRewards: false, the battle pass marks gameplay-affecting rewards never-paid-only, store bundles are cosmetic-only, and the workshop marketplace sells data-only mods. Live service grows the world; it never sells an advantage in it.

What ships, honestly#

The split is clean, and stating it up front keeps the rest of the page honest.

The calendar service is real. apps/v5/live-service-calendar/ is a NestJS + Fastify package (src/main.ts, port 4223) whose controller (src/controller.ts) exposes five GET routes — /feed, /year1, /world-events/year1, /pro-stadium-tour, /year3-pipeline — each requiresJwt: true and regional: true in contract.json, each delegating to handleRealRequest in the shared @v5/service-shared runtime. A 172-line contract.test.ts pins the endpoint set, the 21 declared capabilities, the port, and the exact shape of every feed payload, plus a success/outage/unauthorized contract-case sweep and a Docker/k8s/failover artifact check.

The UE client tier is real C++. V5/ue/Source/V5OnlineServices (a 1950-line V5OnlineServicesSystems.cpp over a 1215-line types header, with an 809-line test) ships UV5_Online_LiveServiceCalendarClient, UV5_Online_CrossCellPersistentWorld, UV5_Online_FactionRepClient, and the AI highlight-reel builders as UBlueprintFunctionLibrary composers that build season rotations, world-event scripts, the Year-3 pipeline, and the request envelopes the HTTP client sends.

The DLC machinery is real C++. V5/ue/Source/V5Modes (V5ModeSystems.cpp, 639 lines) classifies, discovers, and build-target-gates GameFeature plugins; 29 V5Mode_*.uplugin descriptors sit under V5/ue/Plugins/, and all 29 declare an enabled GameFeatures dependency.

The economy domains are real. @v5/service-shared's WorkshopDomain (domain/workshop.ts) is a Postgres-backed UGC marketplace with a moderation state machine, an ML pre-screen, and an exact-to-the-cent 70/20/5/3/2 revenue split; FactionRepDomain (domain/faction-rep.ts) is a real exponential-decay retention model. The store is a transparent Next.js storefront under apps/v5/web/src/app/store/.

Five honest caveats matter. First, the shipped feed payloads are object literals. handleRealRequest resolves to evaluateServiceSpecificRule (runtime.ts:1818), which returns hand-authored constants — seasonalThemeRotations: 4, scriptCount: 12, stopCount: 6 — not rows read from a database or parsed from the manifests. Second, a real Postgres calendar domain exists but is not on that path. LiveServiceCalendarDomain (domain/live-service-calendar.ts) implements ensureSchema/upsertEvent/ feedEvents/activeEvents and a SHA-256 content-derived etag, but it is imported only by realtime.ts and a test — the deployed /year1 feed does not call it. Third, there is no V5/store directory; the only store surface is the web storefront, which is presentation, not a transactional IAP backend. Fourth, the content is uncooked: a scan of all 29 plugins finds zero binary .uasset and 62 JSON stand-ins — V5, like V4, represents assets and levels as JSON descriptors. Fifth, V5's GameFeature activation is thinner than V4's. V5Modes ships discovery, classification, a static roster, and per-target gating; it does not ship V4's UGameFeatureAction subclass that drives UAssetManager async streaming on activation. The DLC boundary is real; the runtime asset-streaming seam is a recorded step, not a call.

Live-service architecture: the calendar spine#

Post-launch content reaches players through one service and three representations of the same numbers, kept consistent by tests rather than by a single source of truth. The service is the calendar; the representations are the authored JSON manifests, the NestJS literals, and the UE C++ builders.

flowchart TD subgraph Authored["Design source of truth"] JSON[V5/live-service/*.json<br/>season-1 · year-1 · year-3 manifests] end subgraph Service["apps/v5/live-service-calendar (NestJS + Fastify :4223)"] Ctl[controller.ts<br/>5 JWT-gated GET routes] --> HRR[handleRealRequest →<br/>evaluateServiceSpecificRule<br/>authored literal payloads] Dom[(LiveServiceCalendarDomain<br/>Postgres + SHA-256 ETag<br/>real, not on the feed path)] end subgraph Client["V5/ue/Source/V5OnlineServices (C++)"] Build[UV5_Online_LiveServiceCalendarClient<br/>BuildYear1Calendar · Year3 · WorldEvents] Http[UV5_Online HTTP client] end JSON -. authors .-> HRR JSON -. authors .-> Build Build --> Http Http -->|Bearer JWT| Ctl HRR -->|ETag feed| Http HRR -->|/api/public/season-calendar| Web[apps/v5/web public calendar + store] subgraph DLC["V5/ue/Source/V5Modes (C++)"] Plug[29× V5Mode_*.uplugin<br/>GameFeatures dependency] --> Disc[DiscoverGameFeaturePlugins] Disc --> Gate[GateKnownMode<br/>per build target] end

V5LiveServiceCalendarController is a thin router: each method stamps a requestType/feed pair onto the query and calls V5LiveServiceCalendarService.handle, which builds a ServiceRequest and runs handleRealRequest(slug, request, now). resolveEndpointPath (runtime.ts:1233) maps year1-calendar/v5/live-service-calendar/year1, year1-world-events/world-events/year1, and so on, so the response carries the canonical path back. Every route is JWT-gated and regional, every payload carries clientCacheEtagRequired: true and orderedWindows: true, and the failover block declares regional-active-active across iad/fra/sin primaries with pdx/dublin/syd fallbacks. This is a genuine deployable service with a genuine contract; what it is not, yet, is a database query — evaluateServiceSpecificRule returns the authored constants directly, and the contract test asserts exactly those constants (calendarId: 'calendar.year1.live-service', seasonalThemeRotations: 4). The Postgres LiveServiceCalendarDomain is the shape that path is specified to grow into; today it is real code one wiring step removed from the feed.

Seasons, battle pass & the persistent calendar#

Season One is authored in V5/live-service/season-1-live-service-manifest.json: Fracture Signals, a 90-day window (2027-03-01 → 2027-05-30) opened by a 30-day Continuum Signal day-one event. Its battle pass is 100 tiers at a 6–8 hours-per-week target, with an explicit tierSkipPolicy — skips are purchasable (purchaseEnabled: true, maxSkipsPerSeason: 20) but gameplayAffectingRewards: "never-paid-only", the monetization line drawn in the data itself. Reward families are per-cell cosmetics, battle-pass XP, creator spotlights, and Mind Palace case stamps — never frame data or stats. The manifest also names its sourceServices (the calendar, faction-rep, balance-ledger, and workshop contracts), so the design doc points back at the code that serves it.

The UE side builds the same data in C++. UV5_Online_LiveServiceCalendarClient (V5OnlineServicesSystems.h:260) exposes BuildYear1Calendar, BuildYear1SeasonalRotations, BuildYear1SpecialEvents, and the predicate EvaluateCalendarEventState over constants Year1SeasonDays = 90 and Year1SeasonCount = 4 (V5OnlineServicesSystems.cpp:20). SeasonalRotation (:106) computes each season's start as Year1CalendarStartUtc() + (n-1)·90 days and attaches per-cell themes from BuildCellThemesForSeason — real date arithmetic, not a flag. The /year1 feed literal mirrors it: four seasonal-theme rotations plus the Bureau Founding Day anniversary, the Shelter Signal charity event, and the Platform Circuit console tie-in, all with gameplayProgressionGated: false.

Persistent world events are their own feed. BuildYear1PersistentWorldEvents and BuildYear1WorldEventScripts (V5OnlineServicesSystems.h:311) compose the twelve one-week scripts authored in year-1-persistent-world-events.json — Gang War Week first — and the /world-events/year1 literal pins scriptCount: 12, gangWarWeek: 'world-event.year1.01.gang-war-week', persistentWorldState: true, settlementSnapshots: 12, and crucially exclusiveGameplayRewards: false: a player who misses Gang War Week loses no power, only a moment. The Year-3 pipeline (BuildYear3LiveServicePipeline, BuildYear3SubCellPlans with a DlcGate per sub-cell, BuildYear3CrossCellCoopMissionPlans, BuildYear3ConsoleSkuPlan) plans four sub-cells, eight cross-cell co-op missions (Year3CrossCellCoopMissionCount = 8, …Minutes = 240), and one sku.year3.console.switch2-native-complete SKU with a first-party cert track and reduced-install budgets — a roadmap feed, honestly labelled contentLock: year-3-planning. Each builder also returns a Get…ManifestPath() string so the companion app and the /api/public/season-calendar web surface can resolve the same authored JSON the C++ encodes.

Faction-rep decay, highlight reels & retention#

Retention has real domain logic behind it. FactionRepDomain (domain/faction-rep.ts) relaxes reputation toward a neutral floor while a player is inactive: decayedRep(rep0, daysInactive) returns REP_FLOOR + (rep0 − REP_FLOOR)·exp(−ln2/halfLife · effectiveDays) with REP_FLOOR = 0, REP_CEILING = 1000, DEFAULT_HALF_LIFE_DAYS = 30, and a DEFAULT_GRACE_DAYS = 14 window below which nothing decays. The surplus over the floor halves every 30 idle days; a weekly runDecay(nowUnix) journals every decrement into an append-only ledger. The file's own header records that this "replaces the prior simulatedDecay: true" handler — a real de-fabrication, not a flag pretending to be a model. The UV5_Online_FactionRepClient::BuildYear1DecayPolicy builder surfaces the same policy to the client, and year-1-faction-reputation-decay.json authors the five restoration mission arcs that let a lapsed player climb back. The AI highlight reel (BuildYear1AIHighlightReel, BuildTop10HighlightClips, HighlightSignal) ranks replay clips into an optional commentator track; its manifest id v5.year1.ai-generated-highlight-reel and the /v5/replays/commentary/tts/year1 route both require synthetic-content indicators and source-replay verification.

DLC delivery: the GameFeature boundary#

A new cell, a new mode, a new DLC expansion — in V5 these are all the same thing: a V5Mode_* GameFeature plugin. The boundary is enforced in V5Modes, and it is real engine-integration C++. IsV5GameFeaturePlugin (V5ModeSystems.cpp:86) accepts a plugin only if its name starts with V5Mode_ and its descriptor references the engine GameFeatures plugin with bEnabled — a two-part predicate, not a name match. DiscoverGameFeaturePlugins (:102) walks IPluginManager's discovered or enabled plugin set, filters through that predicate, de-duplicates with AddUnique, and sorts by FNameLexicalLess so discovery is deterministic across machines. The static roster MakeMode (:41) binds each plugin name to an EV5Cell, an EV5ModeNetworkModel, a HUD surface, a max-player count, and dedicated/listen-server flags — the 29 modes spanning every launch cell plus the Steampunk DLC cell, the editors, and the online/co-op variants.

The DLC cell itself is V5Mode_Steampunk_Detective.uplugin: its descriptor reads "EnabledByDefault": false, "CanContainContent": true, "BuiltInInitialFeatureState": "Installed", an enabled GameFeatures dependency, and the description "Content-bearing GameFeature root for the Brave New World Steampunk Detective DLC cell." EV5Cell::SteampunkDetective (V5ModeTypes.h:41) is a first-class cell, and MakeMode("V5Mode_Steampunk_Detective", …, EV5Cell::Steampunk, …) registers it alongside the launch roster — so the DLC cell is structurally identical to a shipped cell, gated off by default until entitled.

What turns the roster into a per-build catalogue is GateKnownMode (:129), a real build-target predicate over EV5ModeBuildTarget. A DedicatedServer build enables only bSupportsDedicatedServer rulesets; a LANOffline build refuses any mode with bRequiresOnlineServices or a CompetitivePvP/SocialLobby net model; ArcadeCabinet cooks only the curated local co-op roster (IsArcadeCabinetMode); Editor previews everything non-exclusive. This is the fail-closed seam between content and platform: a DLC cell that needs online services will not enable itself in an offline cabinet build. The honest limit, stated above, is that V5 stops at discovery + gating + a roster. It does not ship V4's UV4GameFeatureAction_ActivateModeAssets that issues a real RequestAsyncLoad on activation — and with zero cooked .uasset on disk, the soft content paths resolve to nothing at runtime. The plugin scaffolding, dependency wiring, classification, and build-target logic are real and test-pinned; the streaming activation is a documented gap, not a claimed feature.

The DLC catalogue the architecture enumerates rides this boundary: 20 weekly Cold Cases Mind Palace packs (V5Mode_Detective_ColdCases), Frontier bounty and naturalist drops (V5Mode_Frontier_OutlawTrail), and the full-vision expansion cells — Polynesian and Pirate Frontier, Egyptian and Cyberpunk-2087 sub-cells, the Cosmic-Horror Hunter, and the Steampunk Detective cell — each a plugin-shaped, JSON-manifested content drop owned by its mode, never a binary client patch.

The store & the workshop marketplace#

The retail store is a transparent web surface, not a dark-pattern funnel. apps/v5/web/src/app/store/page.tsx and store/[slug]/page.tsx render storeEntries from lib/publicContent.ts: exactly two entries — the Continuum Edition base game ($69.99, "all five launch cells and cross-progression", a nine-platform entitlement map) and the Founders Field Kit ($19.99, "cosmetic-only … profile frames, camp props, and ship decals", tagged "No power"). The store copy's stated job is to "separate gameplay access from cosmetics … without pressure tactics" and keep "balance-affecting rewards out of paid bundles" — the same player-protective constraint expressed as content rather than as a checkout flow.

The transactional economy with real backend logic is the workshop marketplace. WorkshopDomain (domain/workshop.ts) persists UGC items through a moderation state machine: a publish runs prescreenRisk(reportCount, flaggedTerms) and moderationFromRisk to set an initial state, a human moderate call overrides it, and revenueShareSplit computes an exact-to-the-cent payout from REVENUE_SHARE basis points — 7000 creator (70%), 2000 platform, 500 creator fund, 300 chargeback reserve, 200 tax — with a 48-hour REFUND_WINDOW_HOURS. The marketplace endpoints (/v5/workshop/marketplace/{listings,purchase,revenue-share,payouts}, runtime.ts:1220) are the paid data-only mod marketplace the architecture flags as [P3], and they are the closest thing in V5 to a real store ledger — a creator economy, not a loot box.

The honest line: no pay-to-win, no binary hot-patch#

Two invariants run through every system on this page. No gameplay power is sold or seasonal-gated. It is asserted in the calendar literals (exclusiveGameplayRewards: false, gameplayProgressionGated: false), in the battle-pass manifest (gameplayAffectingRewards: "never-paid-only"), in the store copy ("No power"), and in the cosmetic-only reward families — the same hard competitive-integrity line V2's player-protective store and V4's sim-isolated cosmetics draw. And hot-fixes are data, not binaries. The architecture's "balance + UI strings via CDN-served data files; no binary patch" is expressed as the calendar's hotfix-window capability and hotfix-windows feed, paired with the append-only balance-ledger service whose every entry is telemetry-evidence-linked. A balance change ships as a calendar-scheduled data window and a ledgered tuning record, never as a forced client rebuild — which is also why a DLC cell can be EnabledByDefault: false and simply flip on when entitled.

Where this connects#

  • The backbone underneath: Persistence, Online Services & Cross-Play — the 16-service V5OnlineServices client catalog, the @v5/service-shared NestJS tier, and the Bureau account/XP/cosmetic ledgers that the seasonal calendar, world events, workshop payouts, and faction-rep decay all read from and write to.
  • The guardrails around it: Accessibility, Security & Compliance — the JWT auth, spend-limit and store-compliance posture, synthetic-content indicators on AI highlight reels, and the moderation/DSAR path behind the workshop marketplace.
  • Platform foundations: Oshun Domain Libraries — the shared economy, governance, and content substrates the V5 store, workshop, and live-ops services are specified to compose rather than reinvent.
  • The catalogue index for the whole V5 architecture set is ../V5_ARCHITECTURE.md.