Skip to content

World Systems

Summary: Project Eternal's world systems include the generation-native automap (UAutomapStateComponent — a server-built replicated projection of the dungeon layout, revealed by a containment scan and drawn as a Slate-painted parchment floor plan), procedural level generation via the topology-based Dungeon System (UEternalDungeonGenerator, see Dungeon System), and global event tracking in UQuestManagerComponent. Replicated state uses FFastArraySerializer for efficient networking. Cosmetic layers — passive world-space tutorial hints (AWorldHintActor) and preset-driven ground fog mood (AGroundFogVolume) — are client-only/non-replicated and layer over GAS, the quest system, and the lighting subsystem.

Table of Contents


Why This Architecture

Design Goals

The world systems are built around four principles:

  1. Zone Independence - Each zone maintains separate exploration data
  2. Efficient Replication - Delta-compressed arrays minimize network traffic
  3. Server Authority - All state changes originate on server
  4. Data-Driven - Procedural generation uses seed-based determinism

Component Ownership

Component Owner Why
UAutomapStateComponent GameState Party-shared map projection + reveal (decision: exploration is run-scoped and shared)
UQuestManagerComponent GameState Global event tracking

System Architecture

+------------------------+
|    AEternalGameState   |  (Server Authority)
+------------------------+
        |
        +-- UQuestManagerComponent
        |       |
        |       +-- Quest Registry
        |       +-- Global Events (FGameplayTagContainer)
        |
        +-- UAutomapStateComponent
                |
                +-- FAutomapLayoutHeader (epoch + transform, ReplicatedUsing)
                +-- FAutomapNodeArray   (FastArray: footprints, reveal, state)
                +-- TArray<FAutomapDoorEdge>
                +-- FAutomapMarkerArray (FastArray: actor markers)

Replication Strategy

System Replication Type Why
Automap projection + reveal Shared FastArrays, all COND_None Party-shared exploration; GameState has no owning connection, so COND_OwnerOnly here replicates to NOBODY on a dedicated server
Global Events FGameplayTagContainer Lightweight tag replication
Floor Data Not replicated Deterministic seed recreation

Automap (Generation-Native v2)

The old scene-capture/fog automap (UMapExplorationComponent, UWorldPOIManagerComponent, AAutomapCameraActor, render targets, radial fog masks, zone-name strings) was clean-replaced in 2026-08. Design source of truth: ImplementationDocs/AutomapRedesign.plan.md (13 ratified product decisions, REQ-1..23 — the plan is binding for any change here). Governing principle (decision 12): the map is a memory, not an oracle — it records only what the player has witnessed in-world. No room-kind tints, no unrevealed intel; a future map feature must pass "would the player know this from standing there?".

Data Plane — UAutomapStateComponent (GameState)

The server flattens the two layout datasets (semantic topology in UDungeonSubsystem ⊕ the ProceduralDungeon plugin's spatial graph) into one compact, immutable, replicated projection, built once per dungeon from the post-generation callback (never from observed room counts — generator retries alias):

Piece Shape Notes
FAutomapLayoutHeader ReplicatedUsing struct Epoch LayoutId (new FGuid per dungeon), expected counts (completeness gate), lattice transform + RoomUnit, floor origin, domain tag
FAutomapNodeArray FastArray Per node: dense NodeId, XY footprint (max-exclusive), Z span, kind (Content/Environment/RemnantRealm), flags (reveal bit + witnessed room state), structural MarkerTag
DoorEdges plain replicated array Node pairs + door cell/direction; edges on the boss node render as the boss threshold
FAutomapMarkerArray FastArray Actor markers, epoch-stamped (stale node ids resolve to real-but-wrong rooms otherwise), optional world-XY hub path + quest gates

Key invariants (each traces to a REQ; tests in Private/Tests/Automap/): - Everything replicates COND_None — GameState has no owning connection. - Completeness gate: clients never build a map from partially arrived arrays; HasMap() also requires ExpectedNodeCount > 0 so a reset layout is never trivially "complete". - Listen-server funnel: every mutation notifies through shared Handle*Changed methods called from both the authority mutator and the replication callbacks (OnReps don't fire for the host). - Reveal is a ~4 Hz server containment scan (pawn world position → lattice cell → node), NOT door events — door-based room tracking is single-player-global and freezes on locked-room rejections. Spectators/dead players don't reveal; the scan holds during transitions. - Initial burst is budgeted (~5 KB/client/dungeon); BuildProjection logs the estimate and a spec asserts a worst-case dungeon stays inside it.

Markers

  • Structural (entrance/exit/boss/landmark/Remnant Realm portal) are resolved server-side into node MarkerTags at build time — zero registration, the "POI didn't register" failure class no longer exists.
  • Actor markers: UAutomapMarkerComponent on the actor (NPCs, portals, objective devices). Dual-path server registration — the component push-registers on BeginPlay (with retry) and the state component pull-scans levels as they add to the world; the MarkerId derives from the owner's pathname so both paths are one idempotent registration. Static-position by contract.
  • Quest pins (decision 9): the replicated entry carries only world facts (VisibleWithQuestTag / HiddenByQuestTag); each client derives pin visibility from its OWN quest state at plan-build time, fail-closed.

Rendering + UI

Client-side: UAutomapController binds to the GameState component on a retry ticker (the GameState changes on every travel) and rebuilds FAutomapPlan (presentation struct) on change; SAutomapCanvas paints it — parchment ground, ghost plates for adjacent unentered rooms, a merged uniform wash for revealed space, an ink boundary hull with gaps at connection spans (decision 13: passage = gap in the ink, no door glyphs), witnessed-state tints, marker sigils with screen-space declutter (merge radius + count badge), edge chevrons for off-view markers, party arrows, player arrow. Two surfaces: - UAutomapMinimapWidget — always-on HUD corner minimap, player-centred, camera-aligned (the view rotates by the gameplay camera's world yaw so screen-up on the map = screen-up in the world). - UAutomapOverlayWidget — the N-key overlay (Tab is target cycling) with drag/wheel and gamepad pan/zoom; fits to revealed bounds only so the dungeon's size never leaks. Its input actions live in IMC_Game_Default — the Gameplay IMC is removed while a panel is open. - Hubs (no layout): same canvas renders parchment + arrow + world-positioned markers; no floor plan.

Camera alignment is paint-time only — both surfaces rotate the view to the gameplay camera's yaw and foreshorten its depth axis to the camera pitch in SAutomapCanvas::OnPaint (pan input inverts both). The angles are PINNED as style values (ViewYawDeg = 50, ViewDepthScale = sin 30° = 0.5) rather than read from the live camera, so cinematic/debug cameras never warp the map — retune them with the rig in AEternalPlayerCharacter. Map SPACE stays north-locked.

Coordinate derivation (half-cell conventions, north lock) is documented in Public/UI/ViewModels/AutomapPlan.h and locked by golden specs. Debug: Cog window + Automap.RevealAll / Automap.RevealRoom / Automap.ClearReveal.


Procedural Floor Generation

Superseded. The seed-based UFloorUtilities / FSaveFloorData / FSaveRoomData floor generator described here no longer exists. Procedural level generation is now handled by the topology-based Dungeon System (UEternalDungeonGenerator producing FDungeonInstance from UDomainDungeonConfig, room content in UEternalRoomData). See Dungeon System for the current architecture, room configuration, and boss/landmark placement.


Global Event System

Event Architecture

UQuestManagerComponent
    |
    +-- ActiveGlobalEvents (FGameplayTagContainer)
    |       |
    |       +-- Replicated with OnRep
    |
    +-- QuestRegistry (TMap<Tag, DataAsset>)
    |
    +-- Delegates
            +-- OnGlobalEventStarted
            +-- OnGlobalEventEnded

Event Operations

Operation Authority Effect
StartGlobalEvent(Tag) Server only Add tag, broadcast start
EndGlobalEvent(Tag) Server only Remove tag, broadcast end
IsGlobalEventActive(Tag) Any Query current state

Event Flow

Server: StartGlobalEvent(Event.Boss.Phase2)
    |
    v
ActiveGlobalEvents.AddTag()
    |
    v
OnGlobalEventStarted.Broadcast()
    |
    v
Replication to clients
    |
    v
Client: OnRep_ActiveGlobalEvents()
    |
    v
Client systems react to new event

Common Event Tags

Tag Purpose
Event.Boss.Phase1 Boss phase tracking
Event.World.Invasion World event active
Event.Merchant.Special Special vendor available
Event.Portal.Open Portal activated

World Hint System

Why This Exists

The world hint system delivers passive, diegetic tutorialization: rather than a modal popup, a mechanic is taught in-place by a level-authored cue that appears when the player walks near it and disappears once they actually perform the taught action. A hint is never re-shown after it has been learned, so it stays out of the way for experienced players and returning sessions.

Authority Model — Client-Only

Hints are a client-only / local concern. Display, overlap detection, and completion listening all run on the owning client; nothing about the prompt itself replicates. The single piece that must reach the server is persistence — the learned state is recorded as a quest tag, and the component routes that write through a server RPC so authority owns the player's quest state.

AWorldHintActor (level-placed)
    |
    +-- UWorldHintComponent  (all logic)
            |
            +-- USphereComponent  (DetectionRadius overlap)
            +-- UWidgetComponent  (world-space prompt)
            |
            +-- Completion source --> writes CompletionTag
                                          |
                                          v
                              UPlayerQuestComponent::ServerAddQuestTag()  (authority)

Lifecycle / Data Flow

Player enters DetectionRadius
    |
    v
IsCompletedForPlayer()?  --YES--> stay silent (already learned)
    | NO
    v
Show prompt widget (OnShowPrompt)
BindCompletionListeners(Player)
    |
    v
Player performs the taught action
    |
    v
CompleteHint(Player)
    |
    +-- Write CompletionTag via PlayerQuestComponent (server RPC)
    +-- Hide widget (OnHidePrompt)
    +-- Broadcast OnHintCompleted
    +-- UnbindCompletionListeners

Player leaves radius (uncompleted) --> hide widget + unbind, no persistence

Completion Sources

Completion is triggered one of two ways. Both converge on CompleteHint().

Source How configured Use case
GAS gameplay tag (default) Set TriggerGameplayTag; the component listens for that tag on the player's ASC Mechanic exposed as a gameplay tag (most abilities/actions)
Blueprint override Subclass actor/component, override BindCompletionListeners/UnbindCompletionListeners, call CompleteHint() Non-GAS triggers (e.g. ALS locomotion-action events, inventory delegates)

CompletionTag controls persistence: if set, the learned state is recorded as a quest tag and the hint never shows again; if empty, the hint is non-persistent and re-shows on each overlap. AEternalCharacter carries hooks supporting hint interaction.


Local Ground Fog Volume System

Why This Exists

AGroundFogVolume is a cosmetic, non-replicated actor that wraps the engine's ULocalFogVolumeComponent with a designer-friendly preset authoring layer. Instead of hand-tuning raw fog-component fields per placement, designers pick a named mood preset and optionally tweak the resolved values. It is intended to live in lighting sublevels so each dungeon's fog mood swaps alongside the rest of its lighting, coordinated by the environment subsystem.

Data Flow — Preset → Struct → Component

EGroundFogPreset (designer picks mood)
    |
    v
FGroundFogPresetData::GetDefaults(Preset)   <-- hand-tuned defaults per preset
    |
    v
FGroundFogPresetData FogData   <-- designer may override any field per-instance
    |
    v (ApplyToComponent / OnConstruction)
ULocalFogVolumeComponent   <-- DensityScalability multiplier applied for quality scaling

Authoring Contract

  • Preset fills the struct, not the component. Selecting a preset (or ResetToPresetDefaults) overwrites FogData with that preset's defaults; the struct is then the editable source of truth and ApplyToComponent pushes resolved values down to the engine component.
  • Custom preset leaves the struct as-authored (no overwrite), for fully bespoke fog.
  • DensityScalability is a per-instance multiplier on top of the resolved density, used to scale fog cheaply on Med/Low quality settings without re-authoring.
  • Cosmetic & non-replicated — never gameplay-affecting; place in lighting sublevels (see UEnvironmentSubsystem).

Presets

Preset Intent
ThinGroundMist Light low-lying mist
HeavyPool Dense pooled fog
WarmSmokey Warm smoky haze
MoonlitChill Cool moonlit atmosphere
EerieGreen Cursed/ominous green
Custom No preset; struct used as-is

(The procedural wisp fog-card material is an art detail outside this concept doc.)


Source References

Automap

  • UAutomapStateComponent - Source/ProjectEternal/Public/Automap/Components/AutomapStateComponent.h
  • UAutomapMarkerComponent - Source/ProjectEternal/Public/Automap/Components/AutomapMarkerComponent.h
  • Replicated types (header/nodes/edges/markers) - Source/ProjectEternal/Public/Automap/AutomapTypes.h
  • FAutomapPlanBuilder + coordinate derivation - Source/ProjectEternal/Public/UI/ViewModels/AutomapPlan.h
  • UAutomapController / UAutomapViewModel - Source/ProjectEternal/Public/UI/Controllers|ViewModels/
  • Canvas / minimap / overlay widgets - Source/ProjectEternal/Public/UI/Widgets/Automap/
  • Style keys - Source/ProjectEternal/Public/Automap/DataAssets/AutomapStyleDataAsset.h (asset: DA_AutomapStyle)
  • Specs - Source/ProjectEternal/Private/Tests/Automap/
  • Design contract - ImplementationDocs/AutomapRedesign.plan.md

Floor Generation (moved to Dungeon System)

  • UEternalDungeonGenerator - Source/ProjectEternal/Public/Dungeon/EternalDungeonGenerator.h
  • UEternalRoomData - Source/ProjectEternal/Public/Dungeon/DataAssets/EternalRoomData.h
  • UDomainDungeonConfig - Source/ProjectEternal/Public/Dungeon/DataAssets/DomainDungeonConfig.h
  • See Dungeon System for the full source reference list.

Global Events

  • UQuestManagerComponent class - Source/ProjectEternal/Public/Quest/QuestManagerComponent.h:28
  • Event operations - Source/ProjectEternal/Private/Quest/QuestManagerComponent.cpp:56

World Hints

  • AWorldHintActor - Source/ProjectEternal/Public/WorldHint/WorldHintActor.h
  • UWorldHintComponent - Source/ProjectEternal/Public/WorldHint/WorldHintComponent.h
  • Quest-tag persistence - Source/ProjectEternal/Public/Quest/PlayerQuestComponent.h -> ServerAddQuestTag()
  • Character hooks - Source/ProjectEternal/Public/Character/EternalCharacter.h

Ground Fog

  • AGroundFogVolume - Source/ProjectEternal/Public/Environment/Fog/GroundFogVolume.h
  • EGroundFogPreset / FGroundFogPresetData - Source/ProjectEternal/Public/Environment/Fog/GroundFogTypes.h
  • Preset defaults - Source/ProjectEternal/Private/Environment/Fog/GroundFogTypes.cpp -> GetDefaults()

Game State

  • AEternalGameState - Source/ProjectEternal/Public/GameMode/EternalGameState.h:18
  • UEternalGameInstance - Source/ProjectEternal/Public/GameMode/EternalGameInstance.h:12


Recent Changes

Date Change Impact
- Initial documentation Document world systems architecture
2026-03-16 Added World Hint system (AWorldHintActor/UWorldHintComponent) Client-only passive tutorial prompts; overlap-triggered, auto-complete via GAS tag or BP hook, persist via quest tag (server RPC)
2026-05-05 Added Local Ground Fog Volume system (AGroundFogVolume) Cosmetic, non-replicated preset authoring layer over ULocalFogVolumeComponent for per-dungeon fog mood in lighting sublevels
2026-08-02 Automap v2 clean-replace (UAutomapStateComponent + Slate parchment renderer) Old capture/fog/POI machinery deleted; generation-native replicated projection, containment-scan reveal, dual-path marker registry, N-key overlay

Future Considerations

Enhancement Benefit Complexity
Level Streaming Dynamic sublevel loading High
Automap colorblind palette + settings toggle Accessibility (plumbing exists in UAutomapStyleDataAsset) Low
Automap silhouette plates (repurpose UEternalRoomData::PreviewImage) Per-room inked footprints Medium
Automap two-layer canvas + dead-band anchoring Cut minimap repaint cost Medium