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.