# Track note: ui (neumorphic kit, HUD, windows, dashboards) Written by the Coder of the ui track, 2026-10-05. Everything marked "run" was run on this PC in this copy (`D:\HADAL - The Descent\.wt\ui\TheDescentUE`) and the pictures were looked at. Build: zero warnings. ## Progress: all six deliverables are built and verified | Step | State and evidence | |---|---| | 1. Kit | done. `UI/NeuStyle.*` (tokens, generated shadow textures, painter), `NeuIcons.*` (code-drawn item icons), `NeuWidgets.*` (SNeuPanel, Button, Gauge, Slot, Cell, Row, Window, Slider, Progress + Text, Icon, Chip), `DescentUiLayer.*` (UI-unit scaling root), `NeuGallery.*`. Scenario `ui_gallery` PASS: `Saved\Shots\ui_gallery_page.png` shows every widget in every state (normal, hover, pressed, on, disabled, locked, empty, selected, greyed) | | 2. HUD | done. `UI/DescentHudLayout.*` (pure layout) + `UI/DescentHUD.*` (SDescentHUD); created by boot step `UiWidgets` (order `CreateUi`). Scenario `ui_hud_layout` PASS: 11 regions, none intersect, all inside the viewport at 1280x720, 1000x560, 800x450 (+ Sub and Helm at 800x450). Shots `ui_hud_layout_hud_*.png` | | 3. Windows | done. `UI/DescentUiSubsystem.*` (`UDescentUiSubsystem`), `DescentWindows.*` (host, fit, tabs, keys), `DescentPages.cpp` (page layouts), `DescentUiDemo.*`, 17 view-model headers in `UI/ViewModels/`. Scenario `ui_windows` PASS: 19 shots at 1280x720 (PDA x4 tabs, fabricator x6 tabs, refinery, packer, storage, terminal, vat, build, pause, title, end), 8 at 800x450, and the pause window with the Bloom slider enabled; every one reports `ui.window.fits_viewport` | | 4. Dashboards | done. `UI/DescentDash.*`: `SDescentDash`, `SCargoDisplay`, `UDescentDashComponent` (a `UWidgetComponent`), `ADescentDashPanel`. Scenario `ui_dash` PASS: on screen (`ui_dash_dash_screen*.png`) and on panels in an otherwise dark world, lit only by themselves (`ui_dash_world_panels.png`); 8 redraws in 30 frames (8 Hz) | | 5. Commands, state | done. `Descent.UI.Open `, `.Close`, `.Tab `, `.Demo <0 or 1>` in `Debug/DescentCommands_UI.cpp`; state parts `ui.widgets`, `ui.window`, `ui.cargo_display` in `DescentUiSubsystem.cpp` | | 6. Scenarios, tests | done. Scenarios `ui_gallery`, `ui_hud_layout`, `ui_windows`, `ui_dash` in `Debug/Scenarios_UI.cpp`. Tests in `Tests/UITests.cpp`: `Descent.UI.HudLayout`, `.Style`, `.Widgets`, `.Windows`, `.Dash`. Full chain run: `run_tests.ps1` 33 of 33 PASS (28 old + 5 new); scenarios smoke, core_sim, kazah_showcase, kazah_closeups, ui_gallery, ui_hud_layout, ui_windows, ui_dash all PASS | Run (from the project root): `Tools\build.ps1`, `Tools\run_tests.ps1 -Filter Descent.UI`, `Tools\run_scenario.ps1 -Name ui_windows`. Console, in the running game: `Descent.UI.Demo 1`, `Descent.UI.Open pda` (or `map`, `logs`, `status`, `fabricator`, `refinery`, `packer`, `storage`, `terminal`, `vat`, `build`, `pause`, `title`, `end`), `Descent.UI.Tab map`, `Descent.UI.Close`. ## How the other tracks hook in after the merge * **Open and close windows**: `UDescentUiSubsystem::Get(World)->Open(EDescentWindow::Fabricator)` on `E` at a station; `->OpenByName("pda")`; `->Close()`. `IsWindowOpen()` and `IsPaused()` are what the player controller asks (ignore look and move while true). **Nothing opens a window by itself**: Tab, M, B, Esc, P, H and the title card on start belong to the controller's mode state machine. Once a window is open the host closes it on Esc (Tab / M for the PDA, E for stations) and switches PDA/fabricator tabs on Left/Right. * **Fill a window**: write your view-model, then `Ui->NotifyChanged()`: `Ui->Models().Fabricator.Categories = ...; Ui->Models().Refinery.Progress = ...;` A button in a page calls the `TFunction` you left in the struct (`FDescentRecipeRow::OnBuild`, `FDescentSellRow::OnSellAll`, `FDescentPackerViewModel::OnEject`, `FDescentTitleViewModel::OnDive`, ...). Progress bars, gauges and the Bloom slider are bound to the model and move without a rebuild; lists are rebuilt on `NotifyChanged()`. * **HUD**: write `UDescentHudSubsystem::Model()` as before; the HUD reads it. While `Descent.UI.Demo 1` is on, the screen shows a frozen copy instead. * **Input mode**: opening a window shows the cursor and sets `FInputModeUIOnly` on `World->GetFirstPlayerController()` (whatever class it is); closing sets `FInputModeGameOnly`. The pause window also calls `UDescentSimSubsystem::SetPaused(true)` (and resumes only if the UI paused it). * **Dashboards on the Tender**: `UDescentDashComponent* C = NewObject(Tender); C->SetupAttachment(...); C->RegisterComponent(); C->Setup(EDescentDashKind::Main); C->SetWorldWidthM(1.10f);` then `C->SetDashModel(FTenderDashModel)` from `MakeDashModel()` every late step; the cargo display is `Setup(EDescentDashKind::Cargo)` + `SetCargoModel(...)` (0.30 m). Positions of section 4.6 are the Tender track's. `ADescentDashPanel::Spawn` is the ready-made actor. * **Bloom**: the pause window looks up `descent.Look.Bloom` by name each frame; it is disabled with a note while the variable does not exist. ## Deviations from DESIGN_UE.md * Shadows are generated per corner radius (analytic Gaussian edge from the rounded rectangle's signed distance, 9-slice for big rectangles, exact-size textures for circles and slots) instead of four fixed 64 px textures. Same look; the circular gauges need exact sizes. * UI units: 1 unit = 1 viewport pixel up to 1080p, larger screens scale up. The engine's DPI curve (0.67 at 720p) is undone by `SDescentUiLayer`, otherwise the 800x450 layout rules of section 8.5 would draw at 0.41 scale. The project's `UserInterfaceSettings` are not touched. * The narrow rule also makes hotbar slots 48 px and the vehicle panel 226 px wide (the test found a 4 px hotbar/vehicle overlap at 800x450 with 52 and 230). * The HUD has a warning banner under the depth panel (under the radio on narrow screens) for `Warnings.Text`; the table does not place one. * The fabricator's cost chips sit under the description (not beside it) so a row works in a half-width column. * The cargo display has a fill-percentage bar under the tank (the "how full" at a glance) in addition to the header `CARGO 5/8`. * Window sizes are per window (`WindowDesignSize`, PDA 980x640 ... end card 680x460) and scaled down uniformly to fit; at 800x450 the PDA is at 0.67. * `S.Shot` captures without UI (`bShowUI = false`), so the UI scenarios take their pictures with `Descent::Ui::RequestUiShot` (`bShowUI = true`, `UI/DescentUiShot.*`). `Debug\DescentScenario.*` is untouched. ## Shared-file changes None. Everything is in the files the track owns (`DescentTune_UI.h`, `UI\` except the two seam files, `Debug\DescentCommands_UI.cpp`, `Debug\Scenarios_UI.cpp`, `Tests\UITests.cpp`, this note). No module was added: `Slate`, `SlateCore`, `UMG` were already declared. ## HUD-model gaps (the seam files were not touched) * Nothing sets `bUiWindowOpen`; the UI does not write the model, so the controller should (the HUD does not use it). * No model field for the vehicle tool (`DRILL` / `LASER` / `SUCTION`) or for a hold-chunk list: those live in `FTenderDashModel` / `FCargoDisplayModel`. * `Warnings.bHull / bPower / ...` are flags only; the HUD draws `Warnings.Text` for the banner and uses `bCrush` for the red depth and `bLowO2` for the O2 gauge. * Objective folding after 10 s (`J`) is whoever writes `bObjectiveFolded`; the HUD only draws the state. ## Guessed, not run, open (stated plainly) * **Mouse and keyboard in a real window were not tried by a person.** Clicks, drags and key rules are covered by headless tests with synthetic events (`Descent.UI.Widgets`, `Descent.UI.Windows`). That the cursor really appears and game input really stops under `FInputModeUIOnly` was not observed. * The Bloom slider span is `0..max(1, current value)`; the look track's real range for `descent.Look.Bloom` is unknown. Enabled behaviour was shown against a stand-in variable the scenario registers (`descent.UI.ScnBloom`), not the real one, which does not exist in this copy. * The PDA map tab is a stand-in drawing from `FDescentMapViewModel` (coloured cells, markers, two arrows). The real `SSurveyMap` of M9 replaces it. * World markers (beacon / carrier / Tender distance labels) are implemented (projection through the player controller) but no scenario had a marker to show. * The cargo chunk drop (0.3 s) and the full-hold pulse are implemented and the redraw cadence is measured, but no picture caught a chunk mid-drop. * Not measured: frame cost of the HUD and the windows. The empty map is black, so the HUD shots show the shadows on black; on the opaque title card and the dashboards (a `Bg` ground) the dual shadows are plainly visible. * A forced unity build with other tracks' files was not run; file-local names carry a prefix (`Pg`, `UiScn`, `Neu`, `Hud`, `Dash`) to avoid clashes. * Fonts are the engine's Roboto; there is no `Mono` face in the core font, so `ENeuFont::Mono` falls back to Regular. * A new `.cpp` created in the same second as a build can be missed by UnrealBuildTool ("Target is up to date"); touch the file or its folder and build again.