UI & Online · Updated Jun 19, 2026
Menu System — Consolidated Build Plan (Controller-First, Team Slayer)
Menu System — Consolidated Build Plan (Controller-First, Team Slayer)
The actionable north-star for the menu/UI work. It ties together the two existing design docs and adds the two things the game actually needs that they don't cover: a controller-first design contract applied to every screen, and the Team Slayer match UI (scoreboard, team-score HUD, post-match results, lobby match options).
Read order / ownership:
Docs/UISystem.md— CommonUI plumbing: layer model, base classes, the controller-input pipeline, ~28 footguns. ("How a button focuses on a gamepad.")
Docs/MenusAndOnline.md— flow & online: boot→match screen flow, menu inventory, settings model, EOS party/lobby/session architecture, phased build order A–H, hosting abstraction. ("What screens exist, how multiplayer is wired.")
- This doc — the consolidated, sequenced plan with the controller-first contract and the Team Slayer match UI. Where it overlaps, the other two are the source of truth for detail; this is the spine.
Status: PLAN ONLY (2026-06-19). Nothing built. The CommonUI scaffolding (USLPrimaryGameLayout, layer stacks, viewport client, minimal activatable base) exists perUISystem.md§2; everything else is to author.
#1. Guiding principle — CONTROLLER-FIRST (non-negotiable)
This is a couch/controller game. Every screen must be fully operable on a gamepad with the mouse unplugged. Mouse/KBM is a supported convenience, never a requirement. CommonUI makes this achievable if we hold the line — UISystem.md §5 has the mechanics; this is the contract.
#1.1 The contract (applies to every screen, no exceptions)
- Every interactive element is a
UCommonButtonBasesubclass (USLButtonBase) or another
CommonUI-focusable widget. No raw UButton/UImage click targets — they don't take gamepad focus.
- Every activatable screen overrides
NativeGetDesiredFocusTarget()to return a sensible default —
focus must land on a real control the instant the screen opens (never on nothing, never on a tab you didn't mean). See UISystem.md §6.21 — also force-handle the initial input type so a controller-only player has focus on the first menu.
B/ Circle backs out everywhere via CommonUI'sCancelAction(bIsBackHandleron screens, off
on HUD-tier overlays). No screen is a dead end.
- A
UCommonBoundActionBaris present on every screen, showing live, platform-correct prompts
(A Accept · B Back · LB/RB tabs · X/Y context). The player always sees what the buttons do.
- Navigation panels are
VerticalBox/HorizontalBox/GridPanel, notCanvasPanel(canvas doesn't
auto-route gamepad nav — UISystem.md §6.17). Layout is the nav graph.
- No information conveyed only on mouse hover. Descriptions/tooltips surface on focus
(NativeOnAddedToFocusPath), pushed to a shared description area (UISystem.md §6.19).
- Enum/option settings use
UCommonRotator(D-pad left/right cycles in place) — never a
mouse-style dropdown (UISystem.md §6.14).
- Long lists use
UCommonListView(focusable, virtualized) — never a hand-filledVerticalBox
(UISystem.md §6.8). Friends list, scoreboard rows, server/match lists all qualify.
- Focus survives input-method flips — gamepad→mouse→gamepad restores focus
(OnInputMethodChangedNative, UISystem.md §5.2).
#1.2 Per-screen acceptance checklist (Definition of Done for any menu)
A screen isn't done until, with the mouse physically unplugged:
- [ ] Opening it lands focus on a real control (visible highlight).
- [ ] D-pad + left stick reach every interactive element; nothing is stranded.
- [ ]
Aconfirms,Bbacks out, and the action bar shows the correct prompts.
- [ ] Tabs (if any) cycle on LB/RB.
- [ ] Sliders adjust on left/right; rotators cycle; lists scroll and wrap sanely.
- [ ] Focus visual is obvious at a glance (not a subtle tint).
- [ ] Plug a mouse mid-screen, click, then grab the pad again → focus returns.
- [ ] Xbox and PlayStation glyphs render correctly (swap
DefaultInputTypeto verify).
Test discipline: validate each screen on a controller before moving on. The reference project's
OnInputMethodChangedNative+GetDesiredFocusTargetpatterns (UISystem.md§4.1/§5) are what make
this pass — wire them into USLCommonActivatableWidget first (foundation step) so every screen
inherits correct behavior instead of bolting it on per-screen.
#2. The game the menus serve — Team Slayer
Main mode: Team Slayer — two teams (Red/Blue), kills score for your team, first team to the score limit (or highest at time limit) wins. This shapes the lobby options, the in-match HUD, and the post-match screen — the parts MenusAndOnline.md leaves as "existing HUD."
#2.1 What Team Slayer needs from the UI
| Concern | Where | New? |
|---|---|---|
| Team assignment (Red/Blue), shown in lobby + match | Lobby + HUD | New |
| Match options: score limit, time limit, team size, map | Lobby (leader-set) | New |
| Live team scores (Red vs Blue) + match timer | In-match HUD (always-on) | New |
| Full scoreboard (per-player K/D/score, grouped by team) | In-match, hold Back/Select to show | New |
| Personal medal/kill feed (optional first pass) | In-match HUD | Later |
| Post-match results (winner, final scores, per-player stats) | Post-match screen | New |
| Respawn / "you died" + respawn timer | In-match overlay | Partly (death exists) |
These are on top of MenusAndOnline.md's flow; the lobby and in-match sections below extend its menu inventory.
#2.2 Data this assumes (gameplay side — coordinate, not part of menu work)
The UI reads match state; it doesn't own it. Expected on the gameplay/GAS side (likely already partly present via PlayerState/GameState):
ASLPlayerState: team id, kills, deaths, score, display name.
- A
ASLGameState(team-slayer): per-team scores, score limit, time limit / remaining, match phase
(Warmup / InProgress / PostMatch), winning team.
- Delegates the HUD/scoreboard bind to:
OnTeamScoreChanged,OnMatchPhaseChanged,OnMatchTimeChanged.
- This is a dependency to confirm/spec separately — the scoreboard/results UI is only as good as the
replicated match state behind it. Flag for a "Team Slayer game-state" work item (see §6 Phase T0).
#3. Screen inventory — Team Slayer additions
On top of MenusAndOnline.md §2. All use base classes from UISystem.md §4.3.
| Screen | Base | Controller notes |
|---|---|---|
WBP_SL_PartyLobby (extend) | USLScreenWidget | Add a team panel (Red/Blue member columns) + a leader match-options sub-panel (Map · Score Limit · Time Limit · Team Size — all UCommonRotator). Switch team = a focusable button per member (leader can move players). |
WBP_SL_Scoreboard | USLGameOverlayWidget (or HUD-tier activatable) | Two UCommonListViews (Red/Blue) of player rows. Shown while Back/Select held, or toggled. Non-back-handler. Must NOT steal nav from the pause menu. |
WBP_SL_ScoreboardRow | UCommonUserWidget + IUserObjectListEntry | Name · Score · K · D. Team-tinted. |
WBP_SL_TeamScoreHUD | (HUD overlay element, not activatable) | Red score · timer · Blue score, top-center. Binds OnTeamScoreChanged/OnMatchTimeChanged. Always visible in-match. |
WBP_SL_PostMatch | USLScreenWidget | Winner banner + final team scores + per-player results (UCommonListView). Buttons: Continue (→ back to lobby), Scoreboard detail. Auto-focus Continue. |
WBP_SL_RespawnOverlay (extend existing) | USLGameOverlayWidget | "Respawning in N…" + maybe killcam later. HUD-tier, non-focus-stealing. |
Pause menu (WBP_SL_PauseMenu) stays as in MenusAndOnline.md (Resume · Settings · Leave Match) — but "Leave Match" in Team Slayer returns to the Party Lobby, not the main menu (you stay with your party). Confirm via WBP_SL_Modal_Confirm.
#4. In-match UI — the gap to fill
This is what neither existing doc details. All of it is controller-first:
- Team-score HUD (
WBP_SL_TeamScoreHUD): always-on, no focus, pure readout. Lives onHUDLayer.
- Scoreboard (
WBP_SL_Scoreboard): bound to hold Back/Select (the genre-standard gamepad - Footgun: don't make it a back-handler or put it on MenuStack — it must coexist with the pause menu.
scoreboard gesture) — not a togglable focus-grabbing screen. It shows over the HUD, takes no focus, and releasing the button hides it. Implement as a HUD-tier widget whose visibility is driven by an input action (IA_UI_Scoreboard, held), not as a MenuStack push (so it doesn't pause/eat nav).
- Post-match (
WBP_SL_PostMatch): a real MenuStack screen (focusable, action bar, auto-focus
Continue). Pushed on MatchPhase == PostMatch. Continue → leave session → Party Lobby.
- Respawn overlay: HUD-tier countdown, no focus.
The scoreboard-on-hold vs pause-menu-on-Start distinction is the main in-match controller design call:
Select/Back = scoreboard (hold), Start/Options = pause (toggle). Two different buttons, two
different lifecycles. Keep them separate or they'll fight over focus/back.
#5. Consolidated, sequenced build order
Merges UISystem.md §7 (plumbing) + MenusAndOnline.md §6 (flow/online) + the Team Slayer UI here, ordered so controller navigation is validated at every gate (each phase ends with the §1.2 checklist on the new screens). The fast path the user wants (settings early) is preserved.
Phase 0 — Foundation (UISystem.md §7 steps 1–5; the controller pipeline) The single most important phase for controller-first: UI input actions + IMC_SL_UI, CommonUI data assets (DT_SL_InputActions, DA_SL_CommonInputData, CD_SL_ controller data for Xbox/PS5/Generic/KBM), enhance USLCommonActivatableWidget (focus target, input config, OnInputMethodChangedNative), USLButtonBase, pop/clear API. Gate: a 2-button test screen is fully gamepad-navigable + glyphs swap.*
Phase 1 — Front-end shell (MenusAndOnline.md §5 + §6 Phase B) USLUIManagerSubsystem (layout ownership off the pawn), front-end map + GM/PC, WBP_SL_MainMenu (Play/Settings/Quit), Pause menu. Gate: navigate Main Menu + Pause entirely on pad.
Phase 2 — Settings (MenusAndOnline.md §6 Phase C — deliver the Invert-Y/sensitivity ask) Profile save-game + live-apply, Gameplay/Controls tab (sliders + Invert-Y), then Video/Audio/Rebind. Rotators for enums, sliders adjustable on pad. Gate: change Invert-Y on a controller, feel it in-game.
Phase 3 — EOS + Party + Session (MenusAndOnline.md §6 Phases D–F) Login/offline fallback, Friends list (UCommonListView), invite-only Lobby + invite toast, Party Lobby, ISLSessionService host/join, get-everyone-into-the-match. Gate: two pads, invite → lobby → match.
Phase T0 — Team Slayer game state (gameplay dependency, coordinate) Confirm/spec ASLGameState team scores + match phase/timer + the delegates §2.2 lists. The UI phases below depend on this replicated state. (Not menu work, but blocks T1–T3.)
Phase T1 — In-match team UI WBP_SL_TeamScoreHUD (always-on) + WBP_SL_Scoreboard/Row (hold Select). Gate: scoreboard on hold, no focus theft, coexists with pause.
Phase T2 — Lobby team/match options Extend WBP_SL_PartyLobby: team panel + leader match-options rotators (Map/Score/Time/TeamSize). Gate: leader sets options on pad; team assignment navigable.
Phase T3 — Post-match WBP_SL_PostMatch results + Continue→lobby. Gate: full post-match flow on pad.
Phase G — LAN / Phase H — GameLift (MenusAndOnline.md §6 G/H) — later, behind ISLSessionService.
Practical interleave (matches MenusAndOnline's note): **0 → 2 (settings, parallel-safe) → 1 → 3 → T0 →
T1 → T2 → T3.** Settings ships value early and needs no EOS; the team-slayer UI lands after there's a
networked match to score.
#6. Open decisions (Team-Slayer-specific; the cross-cutting ones live in the other two docs)
- Scoreboard gesture — hold Select/Back to show (recommended, genre-standard) vs toggle. Confirms
the in-match input model (§4).
- Team assignment — auto-balance on join, or leader-assigns in lobby, or both? Affects the lobby
team panel.
- Match options surface — fixed defaults (score 50, time 10m, 4v4) for MVP vs leader-configurable
rotators from day one. Recommend ship fixed defaults, add rotators in T2.
- Pause in networked match — per
MenusAndOnline.md§8.4 /UISystem.md§6.12: pause menu is a UI
overlay that does not SetGamePaused once networked (only single-player testing pauses).
- Post-match flow — straight back to lobby (recommended) vs map-vote/“play again” loop. MVP: back to
lobby.
- Team colors / accessibility — Red/Blue is the default; plan a colorblind-safe team-color setting
(Settings → Gameplay) since team identification is core to Slayer.
#7. References
Docs/UISystem.md— CommonUI plumbing, base classes, controller pipeline, footguns. Primary for "how."
Docs/MenusAndOnline.md— screen flow, menu inventory, settings model, EOS/party/session, build order A–H. Primary for "what/online."
- Reference UI project
C:\3D-DEV\HaloProject\SystemLink\Source\SystemLink\UI\— portable activatable/button/modal/tab bases (port, don't copy — UE version gap).
- Lyra — CommonUI + team-scoreboard + match-state patterns worth studying (don't copy whole).