Skip to content

UI Rehost Debug Notes (Tank Game)

Context: match end -> rehost travel flow (House -> Battle -> House -> Battle).
Symptom: step 4 UI shows stale/frozen state (minimap, subwidgets) suggesting widgets persisted across travel.

Key conclusions - CommonGame UI policy keeps root layouts alive across travel; widgets can persist if still referenced by the root layout / layer stack. - Our UI assumed reconstruct on travel. Stale world references (MapBackground, MapIconComp, etc.) likely cause "frozen" UI in step 4. - Lyra does not explicitly teardown UI policy; it relies on persistence + rebind.

Changes made (code) - Split widget controller ready delegate: - UTankUISubsystem now exposes OnLocalWidgetControllerReady and OnSimulatedProxyWidgetControllerReady. - ATankHUD::InitOverlay: - Uses existing overlay controller (no creation). - When legacy overlay disabled, skips widget creation but still returns controller. - Still sets controller / viewmodel for all UTankUserWidget in world. - Removed "rekey" logic from UTankUISubsystem::GetOrCreateWidgetControllerEntry. - Added log points across UI init and ready flow (see logs section below). - Added debug dumps in UGameFeatureAction_AddWidgets to list UTankUserWidget instances and their outer chains. - Added minimap debug logs in plugin: - MapIconComponent (create/register logs, stack dumps when duplicated). - MapBackground (BeginPlay/EndPlay logs). - MapViewComponent (BeginPlay/EndPlay logs + map background count). - Added minimap widget activation logs: - UMapFunctionLibrary::DeactivateMinimapWidget / ReactivateMinimapWidget. - Added Docs/plugins/minimap-debugging.md to document plugin edits for revert.

Changes reverted (teardown) - UTankUISubsystem::ClearCachesForTravel: - Removed forced UTankUserWidget::RemoveFromParent teardown pass. - Now only calls HandleWorldChange(nullptr). - ATankHUD::EndPlay: - Removed OverlayWidget / RootWidget RemoveFromParent cleanup.

Log markers added - ATankHUD::InitOverlay logs when using existing controller. - UTankUserWidget logs: - First log per class: verbose (world/outer/viewport/pointer). - Subsequent logs collapsed to a shorter line. - UTankUISubsystem::InitializeViewModelsForPlayerStateInternal logs resolution path. - UGameFeatureAction_AddWidgets::RemoveWidgets logs full widget dumps + outer chains. - Minimap plugin logs as above.

Detailed log inventory (for future removal) - Source/Metal_terra/Private/UI/HUD/TankHUD.cpp - [UIReady] TankHUD::InitOverlay using existing OverlayWidgetController PS=... - [TankHUD] PreInitializeComponents registered as component receiver - [TankHUD] BeginPlay sent GameActorReady event - [TankHUD] EndPlay removed component receiver - Source/Metal_terra/Private/UI/Widget/TankUserWidget.cpp - [UIReady] TankUserWidget NativeConstruct World=... - [UIReady] TankUserWidget NativeConstruct (repeat) Class=... - [UIReady] TankUserWidget OnPlayerStateReady PC=... - [UIReady] TankUserWidget OnPlayerStateReady (repeat) Class=... - [UIReady] TankUserWidget::OnPlayerStateReady calling ApplyOverlayWidgetControllerCoreParams PS=... - [UIReady] TankUserWidget SetWidgetController WC=... - Source/Metal_terra/Private/Core/TankPlayerController.cpp - [UIReady] PreClientTravel World=... NetMode=... URL=... - Source/Metal_terra/GameFeatures/GameFeatureAction_AddWidgets.cpp - [GF AddWidgets] RemoveWidgets: HUD=... Layouts=... Extensions=... - [GF AddWidgets] WidgetDump Item ... OuterChain=... - [GF AddWidgets] WidgetDump Summary Total=... Worlds=... - [GF AddWidgets] WidgetDump World=... Count=... - Plugins/JourneymanMiniMap/Source/MinimapPlugin/Private/MapIconComponent.cpp - [MinimapDebug] MapIcon OnComponentCreated ... - [MinimapDebug] MapIcon OnComponentCreated duplicate candidate ... - [MinimapDebug] MapIcon Existing ... - [MinimapDebug] MapIcon OnRegister ... - [MinimapDebug] Duplicate MapIcon detected; dumping stack. - [MinimapDebug] Stack:\n... - Plugins/JourneymanMiniMap/Source/MinimapPlugin/Private/MapBackground.cpp - [MinimapDebug] MapBackground BeginPlay ... - [MinimapDebug] MapBackground RegisterMapBackground ... - [MinimapDebug] MapBackground EndPlay ... - [MinimapDebug] MapBackground UnregisterMapBackground ... - Plugins/JourneymanMiniMap/Source/MinimapPlugin/Private/MapViewComponent.cpp - [MinimapDebug] MapView BeginPlay ... - [MinimapDebug] MapView Bound Tracker ... - [MinimapDebug] MapView EndPlay ...

Docs updated - Docs/ui/readiness-flow.md updated for local vs simproxy readiness and InitOverlay changes. - Removed UI notes previously added to Docs/multiplayer/rehost/match-end-flow.md.

Findings from logs - Widget dumps show overlay + subwidgets remain alive across travel. - Outer chains point to BP_TankGameInstance / TankPrimaryGameLayout / WidgetTree. - Suggests root layout and layer stacks keep widgets alive; they do not reconstruct on step 4.

New findings (2026-02-02) - CommonUI pools activatable widgets. PopContentFromLayer deactivates and returns widgets to the pool; it does not guarantee destruction. Log evidence shows the same WBP_Overlay instance IDs reused across House ??Battle transitions. - Fresh data is applied, but stale state persists. Logs show ApplyDataToWidget firing with new PlayerState and OverlayWidgetController on each travel. Staleness is coming from widget state, not missing data. - MVVM/PC delegate bindings were a symptom of widget reuse, not the root cause. Widgets persist across travel, so any MVVM field-notify bindings or PlayerController delegates must be explicitly unbound when the widget is deactivated, then rebound on reactivation. - LocalPlayer context registration is in place for UI extensions. This keeps the binding stable across PlayerState swaps, but also means widgets are reused, not re?‘constructed. - Slate recursion errors (WBP_Overlay_C cannot be added...) were caused by repeated re?‘register/duplicate add attempts. Avoid re?‘register loops; only register once per LocalPlayer unless explicitly cleared. - Minimap errors (Accessed None in MinimapInternal_*) occur after travel because internal refs (MapView/MapBackground/MapIconComp) are null during teardown/rebind. The minimap tracker keeps arrays of icons/backgrounds/fogs, so they must be explicitly cleaned on widget deactivation and rebuilt on reactivation; add null guards and rebind logic in OnUIDataApplied / OnActivated.

Update (2026-02-03) - UI loop (House <-> Battle) stable across 3 cycles; stale/frozen HUD not reproduced. - Minimap Accessed None bursts align with ReactivateMinimapWidget and tracker counts show Backgrounds=0 on House or immediately after reactivation; points to new/rebound widget before MapBackground is ready, not leaked widgets.

Open questions / next steps - Decide on approach: 1) "Lyra way": keep UI alive and implement robust rebind on world change. 2) Force teardown: add explicit UI policy/root layout release on travel. - If pursuing Lyra way: add world-change rebind in UTankUserWidget + minimap plugin. - Ensure widgets unbind MVVM delegates on rebind, then rebind to the new ViewModel.