UI Architecture¶
Summary: The UI system uses
UEternalUISubsystem(GameInstanceSubsystem) as the central manager for all UI controllers. Controllers extendUBaseUIControllerto manage widget lifecycle and ViewModel registration. Input routing flows throughUEternalInputSubsystem(LocalPlayerSubsystem) using Enhanced Input.
Table of Contents¶
- Why This Architecture
- System Overview
- Core Subsystems
- Controller Pattern
- Dialogue Runtime
- Input Routing
- Configuration
- Initialization Flow
- API Reference
- Source References
- Related Systems
- Recent Changes
Why This Architecture¶
Design Goals¶
- Persistence Across Levels: UI state must survive level transitions, requiring GameInstance-level storage
- Separation of Concerns: Controllers mediate between game systems and UI widgets
- Data-Driven Configuration: Widget classes and input mappings live in data assets, not code
- Multiplayer Support: UI must work correctly for each local player independently
- Input Consistency: All input flows through centralized subsystems for unified handling
Key Tradeoffs¶
| Decision | Benefit | Cost |
|---|---|---|
| GameInstance Subsystem | UI survives level loads | Controllers live forever, must handle cleanup |
| Controller per UI | Clear ownership, focused logic | Many controllers created at startup |
| Data Asset Config | Change widgets without recompiling | Extra indirection, harder debugging |
| LocalPlayer Input | Correct multiplayer input routing | Two subsystems to coordinate |
System Overview¶
+------------------------------------------------------------------+
| UGameInstance |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| UEternalUISubsystem |
| (GameInstanceSubsystem) |
+------------------------------------------------------------------+
| |
v v
+-------------------+ +------------------------+
| UUIConfig | | Registered Controllers|
| (Data Asset) | | (one per UI feature) |
+-------------------+ | |
| HUDWidgetClass | | - HUDController |
| InventoryClass | | - InventoryController |
| TooltipClasses | | - CharacterController |
| DropCatcherClass | | - (see full list) |
+-------------------+ +------------------------+
|
v
+-------------------+
| Widget Instances |
| (Created on demand)|
+-------------------+
+------------------------------------------------------------------+
| ULocalPlayer |
+------------------------------------------------------------------+
|
v
+------------------------------------------------------------------+
| UEternalInputSubsystem |
| (LocalPlayerSubsystem) |
+------------------------------------------------------------------+
| |
v v
+-------------------+ +------------------------+
| UInputConfig | | Input Handlers |
| (Data Asset) | | |
+-------------------+ | UI Toggles: |
| ToggleInventory | | I -> Inventory |
| ToggleCharacter | | C -> Character |
| MouseActions | | K -> Compendium |
| AbilityActions | | |
+-------------------+ | Mouse Events: |
| LMB -> Broadcast |
| RMB -> Broadcast |
+------------------------+
Core Subsystems¶
UEternalUISubsystem¶
Purpose: Central manager for all UI controllers and shared UI state.
Why GameInstance? UI must persist across level transitions. Player inventory panels, tooltips, and HUD elements shouldn't reset when entering a dungeon.
| Responsibility | How |
|---|---|
| Controller Lifecycle | Creates all controllers on Initialize, destroys on Deinitialize |
| Controller Access | Type-safe getters for each controller type |
| Drop Catcher | Manages full-screen widget for item drops to world |
| Safe Zone Updates | Broadcasts to registered controllers on viewport changes |
| Layout Profile | Resolves EUILayoutProfile (Regular/Compact) from viewport size; see below |
| Game Menu Toggle | ToggleGameMenu(bPressIsAlsoBack) — single choke point for ESC / pad Menu |
Layout Profile (Regular vs Compact)¶
Small screens (Steam Deck 1280x800 and below) resolve to EUILayoutProfile::Compact; everything else is Regular.
The profile is re-resolved on every FViewport::ViewportResizedEvent (RefreshLayoutProfile()), and
OnLayoutProfileChanged fires when a resize crosses the breakpoint. Widgets consume the profile, never raw
resolution, so more profiles (TV, ultrawide) can slot in behind the same seam.
Compact changes two things, both text/visual-only — layout geometry keeps the full desktop design space:
| Knob | Accessor | CVar (default) | Effect |
|---|---|---|---|
| Text scale | GetCompactTextScale() |
Eternal.UI.CompactTextScale (PC default 1.0; Deck device profile sets 1.35; <= 0 disables) |
CommonUI text scale; any CommonTextBlock using a CommonTextStyle asset grows |
| HUD scale | GetCompactHudScale() |
Eternal.UI.CompactHudScale (PC default 1.0; Deck device profile sets 1.2; live sink re-applies to every HUD) |
RenderTransform scale on the HUD's fixed-pixel regions (vitals bars, minimap, objective tracker) via UPlayerHUDWidget::ApplyCompactHudScale() |
ScaleTooltipWidthBox() widens a tooltip's fixed-width box and frame padding by the text scale so margins survive
the larger glyphs (caches authored values so re-constructs don't compound).
Game Menu Toggle Contract¶
ToggleGameMenu(bool bPressIsAlsoBack): keyboard ESC is also the menu's Back action, so it passes true and the open
panel's Back handler owns closing (no double-fire). The gamepad Menu button (and console) is not Back, so it passes
false and closes through GameMenuController->StepBack().
UEternalDisplaySettingsSubsystem¶
GameInstance subsystem owning brightness (0..1, 0.5 = neutral 2.2 gamma, applied through the custom tonemapper's MPC)
and a two-profile quality setting (0 = Low, 1 = Medium — the same look-identical profiles boot auto-detect picks
between). Choices persist to GameUserSettings.ini, are re-applied on HandlePostLoadMap, and a user quality choice
overrides the boot benchmark (explicit -ScalabilityLevel= still wins). Source:
Source/ProjectEternal/Public/Settings/EternalDisplaySettingsSubsystem.h.
UEternalInputSubsystem¶
Purpose: Centralized input routing for UI and gameplay. The subsystem is the facade (mapping contexts, device
switch, key labels, delegates, smart-cursor registry); the Enhanced Input handlers live in two objects it owns —
UEternalCombatInputHandler (attacks, combo/charge, aim stick, ability slots, dodge/sprint/block) and
UEternalWorldInputHandler (panel toggles, automap pan/zoom, interact, camera zoom, HUD/console chords).
Why LocalPlayer? Each local player (split-screen, PIE) needs independent input handling. Mouse events broadcast as delegates so widgets can subscribe without coupling.
| Responsibility | How |
|---|---|
| UI Toggles | UEternalWorldInputHandler binds keys (I, C, Tab, etc.) to controller show/hide; pad panel cycling via HandlePadPanelCycle |
| Mouse Broadcasts | OnLeftMouseClick, OnRightMouseClick delegates |
| Press/Release Signals | OnInputTagPressed / OnInputTagReleased per slot tag; OnInputStateReset when gameplay input is suspended |
| Action Binding | BindInputActions binds Move + mouse clicks itself, then each handler's Bind() |
| Device Awareness | GetCurrentInputType() / IsUsingGamepad() + OnActiveInputMethodChanged (mouse <-> gamepad) |
| Smart Cursor Policy | ShouldSmartCursorDrive(), snap-grid registry, seed/warp points for the stick-driven cursor — see CommonUI Integration |
| Aim Stick | GetAimStickVector() raw deflection; DetectAimFlick() pure flick detector (right-stick loot browse / target cycle) |
Controller Pattern¶
UBaseUIController¶
Controllers serve as the bridge between game systems and UI widgets. They own ViewModels and manage widget lifecycle.
+------------------+
| Game Systems | (Combat, Inventory, Equipment, Abilities)
+------------------+
|
| Events / Callbacks
v
+------------------+
| Controller | (UBaseUIController subclass)
+------------------+
|
| Creates & Updates
v
+------------------+
| ViewModel | (Data container)
+------------------+
|
| Binds to
v
+------------------+
| Widget | (UUserWidget subclass)
+------------------+
Controller Responsibilities¶
| Responsibility | Description |
|---|---|
| Widget Creation | Uses DefaultWidgetClass from UIConfig |
| Widget Lifecycle | Show/Hide/Remove widgets by name |
| ViewModel Creation | NewObject<>, Initialize(), RegisterViewModel() |
| Game Binding | Subscribe to component events, update ViewModels |
| Player Ownership | Track owning player for widget context |
Active Controllers¶
This table mirrors the contents of
Source/ProjectEternal/Public/UI/Controllers/— regenerate it from that directory when controllers are added or removed. Do not hardcode a controller count elsewhere in this doc.
| Controller | Purpose |
|---|---|
| UAreaTitleController | Area name display on zone entry |
| UAttributeHUDController | Player attribute display (Health, Stamina, Resonance); extracted from HUDController |
| UBossHealthBarController | Persistent boss health bar; shows while a living boss actor exists, reads the boss ASC's replicated attributes so it works on clients |
| UAutomapController | Slate-painted parchment floor plan — full-screen overlay plus corner minimap, actor markers, quest pins, party arrows, camera-aligned view with pitch foreshortening. See Automap (Generation-Native v2) |
| UCharacterController | Character stats and equipment |
| UCharacterSelectController | "Camp at the Rim" character-select screen; preset entry cards, routes Confirm to StartGamePreset |
| UCompendiumController | Game encyclopedia |
| UCraftingController | Crafting interface |
| UDialogueController | Dialogue playback loop — see "Dialogue Runtime" below |
| UDungeonEdictController | Dungeon modifier HUD (collapsed sigils / expanded details) |
| UEndOfDemoController | End-of-demo screen shown once when the finale realm collapse reaches full darkness; above the transition layer, single exit to main menu |
| UEventBannerController | Large centered banner for major events (awakening, boss kills); queues concurrent requests |
| UGameMenuController | In-game menu (ESC / Menu button); Change Preset returns to character select; inline confirm for unsaved run progress |
| UGlyphController | Glyph socketing |
| UGrantedPowersController | HUD granted-powers tray and character-sheet power lists; pushes immutable power snapshots |
| UHUDController | Main gameplay HUD (health, stamina, skill bar) |
| UInteractionController | World interaction prompts |
| UInventoryController | Inventory management and essence display |
| UItemTooltipController | Item hover tooltips |
| UMainMenuController | Main menu widget and navigation actions (e.g. Start Game) |
| UModalController | Reusable Modal widget (W_Modal); single- and multi-page modal sequences resolved from authored content by id — see "Modal Content" below |
| UNotificationController | Notification overlay (ZOrder 100, always above other UI) |
| URemnantDesireTrackerController | Feeds Sealed Remnant items with near-complete Desires into the objective tracker panel; "nearing awakening" toast |
| UScreenEffectsController | Owner-side low-health/death screen FX; folds ASC health deltas through intensity model and accessibility CVars |
| USkillTooltipController | Ability hover tooltips |
| USkillbarController | Skill slot display and ability tracking; extracted from HUDController |
| UStatusController | World-space status widgets (damage numbers, health bars); extracted from HUDController |
| UStatusEffectController | Status effect panel display; extracted from HUDController |
| UStatusEffectTooltipController | Buff/debuff tooltips |
| UTransitionController | Screen transitions |
| UVendorController | Vendor trade interface |
| UWorldMapController | World map navigation |
Note: some entries (AttributeHUD, Skillbar, Status, StatusEffect, GrantedPowers, RemnantDesireTracker, ScreenEffects) are plain UObject sub-controllers owned by other controllers rather than UBaseUIController subclasses.
Sub-Controller Rebind Contract¶
UHUDController outlives the run, so SetupSubControllers() calls ShutdownSubControllers() first, every time.
Without that, a second world in the same GameInstance stacks the previous run's delegate bindings and shared-ViewModel
entries (Remnant desire rows keyed by dead item GUIDs) on top of the new run's.
The same discipline applies to any sub-controller that binds to a PlayerState-scoped source.
UStatusEffectController::Shutdown removes its OnActiveGameplayEffectAddedDelegateToSelf /
OnAnyGameplayEffectRemovedDelegate handles because the ASC lives on the persistent PlayerState — it survives the
pawn, so every respawn or travel would otherwise leak a listener pointing into a dead panel ViewModel.
Respawn Rebinding¶
AEternalPlayer::OnUnPossess tears the pawn-scoped UI down (hide HUD, unbind input, clear the audio listener), so
every repossession has to re-drive it. AEternalPlayer::AcknowledgePossession runs on the local side of every
possession — listen host and client alike — and calls RebindPawnBoundUI(), which:
| Step | Why |
|---|---|
| Rebind input first, independent of pawn type | Even a non-combat pawn (spectator, scripted) needs movement and UI toggles; gated on the subsystem's bound state so a pure client (which never unbinds) doesn't double-register handlers |
| Bail out for pawns with no combat component | Nothing character-scoped to rebind |
| Bounded next-tick retry until GAS links resolve | The pawn's ASC and AttributeSet resolve a few ticks after possession; binding early builds the skillbar and HUD sub-controllers against a null AttributeSet. The retry handle coalesces rapid possession chains |
Set the skill-tooltip combat-component source before ShowHUD() |
Skillbar reconstruction builds its tooltips immediately, and a stale (dead-pawn) combat component has no AttributeSet |
PlayerState-scoped controllers (inventory, equipment, quests) are untouched by the swap.
Panel Slot Exclusivity¶
Gameplay panels declare a screen region via EMenuPanelSlot (Left / Right / Full) and the subsystem enforces
mutual exclusion in TogglePanel so panels never stack on each other:
| Rule | Effect |
|---|---|
| At most one panel per side | Opening a second Left panel closes the first |
Full conflicts with everything |
A full-screen panel closes all others, and cannot open beside one |
Inventory is the sole Right tenant |
Item-adjacent panels (character, crafting, vendor) sit Left so they can stay open beside the inventory |
GetPanelSlot(EMenuPanel) is the single mapping; PanelsConflict(A, B) is the predicate.
Change Preset / Return to Preset Select¶
UEternalUISubsystem::ReturnToPresetSelect() leaves a run cleanly. Everything else dies with the non-seamless
travel, so it only has to clear GameInstance-lifetime state:
- Clear
PendingPresetandPendingStartNodeOverrideon the GameInstance. UWorldMapSubsystem::ClearWorldMap().CloseAllGameplayPanels(), then hide the game menu.- Force-empty
GameplaySuppressorsand broadcast suppressionfalse— closing panels pops their suppressors, but a straggler left in the set would keep gameplay input muted into the next run. - Set
bRouteToCharacterSelectOnMenuLoadso the menu map opens on character select rather than the main menu. OpenLevel(UIConfig->MainMenuMap).
Bails out with an error if MainMenuMap is unset on the UIConfig.
Dialogue Runtime¶
The DlgSystem playback loop is C++-owned (migrated from the BP_PC "Dialogue" ubergraph page, 2026-07).
Why controller-owned: any actor that can talk only needs a Dialogue asset assigned — no per-actor
Blueprint graphs, and headless narrative authoring exercises the shipped code path.
Talking actor (local interaction path only — never server-side)
AEternalNPC · AQuestTriggerActor · ALoreFragmentActor (native auto-start)
Cinematics / widget buttons (UDialogueStatics, one node)
│
▼
┌──────────────────────────────┐ ┌─────────────────────────────┐
│ UDialogueController │ ShowNode│ W_Dialogue (BP visuals) │
│ • UDlgContext lifecycle ├────────▶│ • renders line + options │
│ • HUD hide + input suppress │StopVoice│ • plays voice-over (interim)│
│ • OnDialogueStarted/Ended │ │ • buttons → DialogueStatics │
└──────────────────────────────┘ └─────────────────────────────┘
| Entry Point | Purpose |
|---|---|
UDialogueController::StartDialogue/SelectOption/Advance/TryEndDialogue |
The loop (C++ callers) |
UDialogueStatics::StartDialogue/SelectDialogueOption/AdvanceDialogue/EndDialogue |
Single-node Blueprint entry points |
Interim widget model: W_Dialogue still reads the UDlgContext handed to it via
UDialogueWidget::ShowNode and owns voice-over playback. UDialogueViewModel is populated every step so
a later Designer pass can bind the widget MVVM-style and retire the Context path
(see ImplementationDocs/archive/DialogueRuntimeMigration.plan.md §Deferred follow-ups).
Input Routing¶
UI Toggle Flow¶
Player presses 'I'
|
v
EnhancedInputComponent
|
v
UEternalWorldInputHandler::HandleToggleInventory()
|
v
UEternalUISubsystem::ToggleInventory()
|
v
UInventoryController::ToggleInventory()
|
v
ShowWidget() / HideWidget()
Mouse Event Flow¶
Player clicks LMB
|
v
EnhancedInputComponent
|
v
UEternalInputSubsystem::HandleLeftMouseClick()
|
v
OnLeftMouseClick.Broadcast()
|
v
[Subscribed Widgets]
- ItemWidget::OnLeftClick()
- CraftingWidget::OnLeftClick()
- etc.
Configuration¶
UUIConfig (Data Asset)¶
Widget class references configured in editor, loaded at runtime. Categories below mirror the Category on each
UPROPERTY in UIConfig.h — regenerate this table from that header when properties are added or removed.
| Category | Properties |
|---|---|
| HUD | HUDWidgetClass, AreaTitleWidgetClass, EventBannerWidgetClass, DungeonEdictBarWidgetClass |
| Inventory / Character | InventoryWidgetClass, CharacterWidgetClass |
| Systems | CompendiumWidgetClass, CraftingWidgetClass, DialogueWidgetClass, GlyphWallWidgetClass |
| Automap | AutomapOverlayWidgetClass |
| World Map / Transition | WorldMapWidgetClass, TransitionWidgetClass |
| Vendor | VendorWidgetClass |
| Menus | MainMenuWidgetClass, CharacterSelectWidgetClass, GameMenuWidgetClass |
| Notifications | NotificationOverlayWidgetClass, NotificationWidgetClass |
| Tooltips | ItemTooltipWidgetClass, SkillTooltipWidgetClass, StatusEffectTooltipWidgetClass |
| Utility | DropCatcherWidgetClass, ModalWidgetClass |
| Items | ItemWidgetClass |
Non-widget data references on the same asset:
| Category | Property | Purpose |
|---|---|---|
| Data | AutomapStyle | Automap canvas palettes, strokes, sigils |
| Data | ScreenEffectsConfig | Low-health vignette / big-hit pulse tuning |
| Data | ModalContent | Authored modal text addressed by id (see below) |
| Data | DefaultGameMap, MainMenuMap | Level targets for Start Game and Change Preset |
| Data | MenuWorldMapData, EraDisplayData | Character-select Domain panel and era badges |
Modal Content¶
Modal text is data, not code. UModalContentDataAsset holds a TMap<FName, FModalSequence>; an
FModalSequence is { Pages, bShowOncePerSession } and each FModalPage carries Title, BodyText (rich-text
markup allowed), ConfirmText and CancelText — empty button text hides that button, and on any page but the last a
button advances instead of closing. UModalController resolves a sequence by FName key from
UIConfig->ModalContent.
| Consequence | Detail |
|---|---|
| Adding a modal is content-only | Author a new key + pages; code only needs the id |
| Once-per-session is data | bShowOncePerSession per sequence, tracked at runtime as a TSet<FName> of shown ids. Not saved, so it replays each launch |
| Runtime-text modals bypass it | The death screen and anything whose text is only known at runtime still call UModalController::ShowModal directly and are not listed in the asset |
Startup-modal gate: the disclaimer/intro modals are gated by the CVar Eternal.UI.ShowStartupModals
(default !WITH_EDITOR), replacing the old #if !WITH_EDITOR compile guards — so the packaged modal path can be
exercised from PIE by flipping the CVar.
UInputConfig (Data Asset)¶
Input action mappings for all UI and gameplay inputs.
| Category | Actions |
|---|---|
| UI | ToggleInventory, ToggleCharacter, ToggleCompendium, ToggleSettings |
| Mouse | LeftMouseClick, RightMouseClick |
| Abilities | AbilityInputActions (tagged array for dynamic binding) |
| Movement | MoveForward, MoveRight, Sprint, Jump |
Initialization Flow¶
Game Instance Created
|
v
UEternalUISubsystem::Initialize()
|
+-- LoadUIConfig()
| Load UUIConfig from UEternalUISettings path
|
+-- InitializeUISystems()
Skip on dedicated server
|
+-- Create all controllers (see Active Controllers table)
| NewObject<UHUDController>()
| NewObject<UInventoryController>()
| ...
|
+-- Create DropCatcherWidget
Full-screen item drop target
|
v
Player Joins / Pawn Spawned
|
v
AEternalGameHUD::BeginPlay()
|
+-- Get UEternalUISubsystem
+-- OnHUDReady() [BlueprintImplementableEvent]
|
v
UEternalInputSubsystem::Initialize()
|
+-- LoadInputConfig()
+-- BindInputActions(PlayerController)
Combat -> PlayerController
UI Toggles -> InputSubsystem
Movement -> PlayerController
API Reference¶
UEternalUISubsystem¶
| Method | Return | Description |
|---|---|---|
| Get*Controller() | UController | Type-safe getter for each controller |
| RegisterController(Controller) | void | Register for safe zone updates |
| ShowDropCatcher() | void | Display item drop target |
| HideDropCatcher() | void | Hide item drop target |
| GetUIConfig() | UUIConfig* | Access widget class configuration |
UBaseUIController¶
| Method | Return | Description |
|---|---|---|
| Initialize() | void | Set up controller, call OnInitialize() |
| Shutdown() | void | Clean up, call OnShutdown() |
| CreateWidget(Class, Name, Owner) | UUserWidget* | Create and register widget |
| GetWidget(Name) | UUserWidget* | Retrieve widget by name |
| ShowWidget(Name, ZOrder) | void | Add widget to viewport |
| HideWidget(Name) | void | Remove widget from viewport |
| RegisterViewModel(VM, Name) | void | Register with MVVM subsystem |
| SetOwningPlayer(PC) | void | Set player context |
UEternalInputSubsystem¶
| Method | Return | Description |
|---|---|---|
| BindInputActions(PC) | void | Bind all input to player controller |
| UnbindInputActions() | void | Remove all bindings |
| GetInputConfig() | UInputConfig* | Access input configuration |
| GetCurrentInputType() | ECommonInputType | Active device as CommonInput sees it | | IsUsingGamepad() | bool | True while a gamepad is the active device | | ShouldSmartCursorDrive() | bool | Gamepad active AND a spatial (grid) panel is open | | Register/UnregisterSnapGrid(Grid) | void | Item grids register while on screen for cursor magnetism | | GetSmartCursorSeedPoint(Out) | bool | Where the smart cursor starts when it activates (false until a grid has geometry) | | GetSmartCursorWarpPoint(Cursor, Out) | bool | Next destination for the pad's cursor-jump verb (cycles grid groups left→right) | | CancelHeldItemOnPad() | bool | Drops the held inventory item back; true if something was cancelled | | GetAimStickVector() | FVector2D | Raw combat aim-stick deflection | | DetectAimFlick(...) | bool (static) | Rising-edge flick detector (low 0.4 → high 0.85 within 0.15 s, 0.35 s cooldown) |
| Delegate | Signature | Description |
|---|---|---|
| OnLeftMouseClick | FOnLeftMouseClick | Broadcast on LMB press |
| OnRightMouseClick | FOnRightMouseClick | Broadcast on RMB press |
| OnActiveInputMethodChanged | FOnActiveInputMethodChanged | Active device flipped (mouse <-> gamepad) |
Source References¶
| Class | File | Line |
|---|---|---|
| UEternalUISubsystem | Source/ProjectEternal/Public/UI/EternalUISubsystem.h | 1 |
| UBaseUIController | Source/ProjectEternal/Public/UI/BaseUIController.h | 1 |
| UEternalInputSubsystem | Source/ProjectEternal/Public/Input/EternalInputSubsystem.h | 1 |
| UUIConfig | Source/ProjectEternal/Public/UI/UIConfig.h | 1 |
| UInputConfig | Source/ProjectEternal/Public/Input/InputConfig.h | 1 |
| UEternalUISettings | Source/ProjectEternal/Public/UI/EternalUISettings.h | 1 |
| UEternalDisplaySettingsSubsystem | Source/ProjectEternal/Public/Settings/EternalDisplaySettingsSubsystem.h | 1 |
| FEternalSmartCursor | Source/ProjectEternal/Public/Input/EternalSmartCursor.h | 1 |
Related Systems¶
- MVVM Framework - ViewModel binding patterns
- CommonUI Integration - Input handling and widget base classes
- Widget Library - Concrete widget implementations
- Item System - Inventory UI integration
Recent Changes¶
| Date | Change | Impact |
|---|---|---|
| 2026-08-20 | Layout profile, game-menu toggle contract, display settings subsystem, pad input API documented | EUILayoutProfile Regular/Compact + Eternal.UI.CompactTextScale/CompactHudScale; ToggleGameMenu(bPressIsAlsoBack); UEternalDisplaySettingsSubsystem; controller table regenerated (UBossHealthBarController, UEndOfDemoController added) |
| 2026-08-06 | Modal content moved into UModalContentDataAsset |
FModalPage/FModalSequence left ModalController.h; sequences resolve by id from UIConfig->ModalContent, once-per-session bools became a data-driven id set. Adding a modal is content-only; startup modals now gated by Eternal.UI.ShowStartupModals instead of #if !WITH_EDITOR |
| 2026-08-06 | Panel slot exclusivity documented | EMenuPanelSlot (Left/Right/Full) enforced in TogglePanel: one panel per side, Full conflicts with everything, Inventory is the sole Right tenant |
| 2026-08-06 | Respawn / second-run rebinding contract documented | AcknowledgePossession re-drives pawn-scoped UI+input with a bounded next-tick retry until GAS resolves; HUDController::SetupSubControllers shuts down the previous run's sub-controllers; StatusEffectController::Shutdown unbinds its PlayerState-owned ASC delegates |
| 2026-08-06 | Preset menu-availability filtering | Character-select and main-menu listings filter presets on EPresetAvailability; Eternal.PlayPreset deliberately ignores it |
| 2026-08-06 | Automap v2 UI | UAutomapController entry expanded: Slate parchment floor plan, overlay + minimap, markers/pins/party arrows, camera-aligned pitch foreshortening |
| 2026-08-06 | UIConfig property table + controller table regenerated | Table had listed 11 of 26 properties; UGrantedPowersController added, non-existent UPoiseIndicatorController removed |
| 2026-07-29 | UGameMenuController added | In-game menu with Change Preset return-to-select flow; GameMenuWidgetClass on UIConfig; ESC wired through HandleToggleSettings |
| 2026-07-13 | Dialogue playback loop migrated BP→C++ (UDialogueController owns the UDlgContext; native auto-start on NPC/QuestTrigger/LoreFragment; UDialogueStatics BP entry points) |
Talking actors need zero graphs; see Dialogue Runtime section |
| 2026-07-03 | Active Controllers table regenerated from Public/UI/Controllers/ (29 headers); hardcoded controller counts removed |
Table is now the single source; regenerate from the directory on change |
| 2026-02 | Added UDungeonEdictController | Dungeon modifier HUD with collapsed/expanded states |
| 2026-02 | Extracted UNotificationController | Standalone overlay at ZOrder 100, no longer inside PlayerHUDWidget |
| 2026-02 | Controller count 11 → 18 | Added Interaction, WorldMap, AreaTitle, Transition, Vendor controllers |
| 2024-12 | SkillTooltipController uses dependency injection via SetCombatComponent() | Controllers no longer discover CombatComponent from pawn; caller provides it |
| 2024-12 | Ability display types moved to AbilityDisplayTypes.h | Core ability structs (ESkillType, FAbilityDamageDisplayInfo, etc.) now in Abilities module |