The runtime UI
verifiedAgainst 9a2d070 · verifiedOn 2026-09-10 · the automated staleness banner is planned, not built.
In one paragraph
Section titled “In one paragraph”VoxelSandbox.UI is the bottom of the assembly graph — it may reference every runtime assembly, and it does. There is no UI Toolkit, no .uxml, no .uss, no UI prefabs and no scene wiring: every HUD element and menu is a sealed MonoBehaviour that builds its own Unity UGUI (UnityEngine.UI) Image/Text tree in code and registers itself through a [RuntimeInitializeOnLoadMethod(AfterSceneLoad)] hook. Each such *Runtime is deliberately thin: the logic that decides what text to show lives in a separate pure static class (*Model, *Presentation, or a FormatXxx method) with no MonoBehaviour, unit-tested in EditMode. Every runtime caches what it last drew and repaints only the widgets whose model value actually changed that frame. The Kenney art the HUD is skinned with never appears in a UI script — it is wrapped in two project-owned ScriptableObjects (HudVisualSettings, KenneyUiStyle) loaded from Resources.
Figure 1: In-game carried ore assay interface (c2-assay gate verification). Player inspects inventory and triggers CarriedOreAssay to sample 10% of carried ore, returning an evidence-bounded qualitative grade.
Figure 2: The code-bootstrapped UGUI HUD: reticle, hotbar, and ContextualTargetHudRuntime targeting the placed crucible apparatus.
The pieces
Section titled “The pieces”| Symbol | File | Responsibility |
|---|---|---|
GameplayHudRuntime |
UI/GameplayHudRuntime.cs:11 |
Crosshair, hotbar, vital bars, item-name popup, bulk-matter readout. Owns the shared Root transform and Settings accessor (:37) every other HUD piece attaches to |
ContextualTargetHudRuntime |
UI/ContextualTargetHudRuntime.cs:15 |
One line explaining the interaction under the crosshair. Read-only companion to BlockInteractionController |
InventoryPanelRuntime |
UI/InventoryPanelRuntime.cs:12 |
The toggled inventory + crafting panel; built from GameplayHudRuntime.Settings/Root |
DebugOverlayRuntime |
UI/DebugOverlayRuntime.cs:25 |
F3 developer overlay. Create() returns early unless Debug.isDebugBuild (:59) — diagnostics never ship |
VesselRunViewRuntime |
UI/VesselRunViewRuntime.cs:18 |
The vessel after-action card. FormatAccount (:112) is a pure static that throws if a line has no committed ScienceEvent source |
WorldFoundryRuntime / WorldFoundryModel |
UI/WorldFoundryRuntime.cs:14 · WorldFoundryModel.cs:7 |
First-load progress screen; the model turns ChunkSchedulerDiagnostics into a phase + a 0–1 bar, with no Unity-UI dependency |
CarriedOreAssay |
UI/CarriedOreAssay.cs:12 |
Pure static: the player-side assay transaction (sample → read → swap batch). No MonoBehaviour |
CarriedOreAssayPresentation |
UI/CarriedOreAssayPresentation.cs:8 |
Pure static: every player-facing assay string, one path shared by the HUD card and the P-gate capture harness |
PlayerAssayRuntime |
UI/PlayerAssayRuntime.cs:15 |
The persistent MonoBehaviour owner that holds the last reading and drives CarriedOreAssay from input |
MainMenuUIRuntime, PauseMenuRuntime, SaveSlotMenuRuntime |
UI/*.cs |
Menus, same bootstrap + code-built pattern |
SaveStatusHudRuntime, NetworkActionStatusHudRuntime |
UI/*.cs |
Transient status toasts |
HudVisualSettings |
UI/HudVisualSettings.cs:7 |
Project-owned ScriptableObject: source Texture2Ds for crosshair/frame/slot/buttons, lazily wrapped as Sprites. Loaded from Resources/HudVisualSettings.asset |
KenneyUiStyle |
UI/KenneyUiStyle.cs:7 |
Project-owned ScriptableObject: the Kenney font + prompt sprites |
KenneyHudStyleRuntime |
UI/KenneyHudStyleRuntime.cs:7 |
Applies the style asset’s font to the built HUD |
UiClickSoundRuntime |
UI/UiClickSoundRuntime.cs:18 |
Attaches a click sound to built Buttons |
The flow
Section titled “The flow”flowchart TD RES["HudVisualSettings / KenneyUiStyle\nResources ScriptableObject"] RIOL["RuntimeInitializeOnLoadMethod\nAfterSceneLoad — static Create()"] RT["*Runtime : MonoBehaviour\nowns Image / Text tree"] PURE["*Model / *Presentation / FormatXxx\npure static, no MonoBehaviour"] SRC["Player / World / Inventory / Chemistry\nlive game state"] UGUI["UnityEngine.UI\nCanvas, Image, Text objects"] TESTS["EditMode UI.Tests\nassert on strings/state"] RIOL --> RT RES --> RT SRC --> RT RT --> PURE PURE --> RT RT --> UGUI PURE --> TESTS RT -. no scene wiring, no prefab .-> UGUI
Bootstrap. Nothing in a scene references a UI script. Each *Runtime has a private static Create() marked [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.AfterSceneLoad)] (GameplayHudRuntime.cs:42) that bails if an instance already exists (instance != null || FindAnyObjectByType<T>() != null), then new GameObject("[Voxel Sandbox] …", typeof(T)) + DontDestroyOnLoad. Awake sets instance = this; OnDestroy clears it. The Scene Composer’s only UI responsibility is EnsureHudVisualSettings() (GameplaySceneComposer.cs:327) — it populates Resources/HudVisualSettings.asset from the quarantined Kenney PNGs so the HUD has art to load.
Build then diff. A runtime allocates its Image/Text objects once (in Create/Start or a lazy TryBuild), stores them in arrays, and caches the last-drawn model value in displayed* fields. Every frame it recomputes the model value and repaints only on a change: RefreshSlotContents (GameplayHudRuntime.cs:204) skips a slot whose ItemStack equals displayedStacks[index]; ContextualTargetHudRuntime sets label.text only when nextText != displayedText (ContextualTargetHudRuntime.cs:57). No per-frame allocation, no needless layout.
The pure layer. The decision logic is pulled out of the MonoBehaviour into a static class with no Unity-UI dependency: WorldFoundryModel.Evaluate (WorldFoundryModel.cs:42) maps scheduler counters to a phase + progress; CarriedOreAssayPresentation (CarriedOreAssayPresentation.cs:8) produces every assay string; VesselRunViewRuntime.FormatAccount (VesselRunViewRuntime.cs:112) renders the whole vessel card from narrator lines. These are what Assets/_Game/Tests/EditMode/UI/ covers — CarriedOreAssayTests, VesselRunViewRuntimeTests — because a string transform is testable and an Image tree is not.
Vendor quarantine. HudVisualSettings and KenneyUiStyle are the only types that hold references into Assets/ThirdParty/Kenney_*. A runtime calls Resources.Load<HudVisualSettings>("HudVisualSettings") and asks it for a Sprite; it never opens a vendor folder. Swapping the art pack is a change to one ScriptableObject and the composer that fills it, nothing else. (See Docs/ASSET_LEDGER.md.)
Invariants you must not break
Section titled “Invariants you must not break”- No scene wiring. A UI element registers itself with
[RuntimeInitializeOnLoadMethod(AfterSceneLoad)]and a singleton guard. Do not add a UI MonoBehaviour to a scene or a prefab — the Scene Composer would then own UI layout, which it deliberately does not. - Runtime is thin; logic is pure. Any branch that chooses text, a number, or a state goes in a
staticclass with noMonoBehaviourand gets an EditMode test. The*Runtimeonly ownsImage/Textobjects and forwards. - Diff before you draw. Cache the last-drawn value and repaint only on change. Allocating widgets, formatting strings, or setting
.textunconditionally every frame is the mistake this codebase’s HUD avoids everywhere. - The HUD never invents scientific prose. Player-facing scientific text comes from a narrator with a committed
ScienceEventsource;FormatAccountthrowsInvalidOperationExceptionon a line withSourceEventId == 0rather than show it. UI presents evidence, it does not author it (Docs/CHEMISTRY_FOUNDATIONS.md— the after-action evidence boundary). - Vendor art stays behind a settings asset. New HUD art is added to
HudVisualSettings/KenneyUiStyle(and the composer that fills them), never referenced from a*Runtimedirectly. UpdateDocs/ASSET_LEDGER.mdfor any new third-party asset. - Diagnostics guard on
Debug.isDebugBuild. Anything developer-only (DebugOverlayRuntime) must return early fromCreate()in a release build.
Where it connects
Section titled “Where it connects”- Upstream: everything.
VoxelSandbox.UI’sasmdefreferencesCore,Chemistry,Data,Industry,Inventory,Player,World, andUnity.InputSystem. It reads live state from all of them and owns none of it. (Note: the Assemblies & boundaries page still lists UI as excluding Industry — theasmdefnow includes it, forVesselRunViewRuntime. That page is due a correction.) - Downstream: nothing. UI is a leaf. No runtime assembly references it; the Voxel Workshop editor assembly does, only to inspect it.
- Input: menus and toggled panels read the shared gameplay input map via
FirstPersonInputReader(inVoxelSandbox.Player), not a privateInputAction. - Tests:
Assets/_Game/Tests/EditMode/UI/VoxelSandbox.UI.Tests.asmdefreferences onlyChemistry,Inventory,UI(+TestAssemblies) — the pure layer is reachable without the rest of the graph.
See also
Section titled “See also”- Add a HUD element — a worked example building one read-only HUD readout through every convention above.
- Assemblies & boundaries — why UI sits at the bottom of the line.
- Catalogues & authoring — the
ItemCatalog/BlockDefinitionlookups the HUD reads. Docs/MASTERPLAN.md§14 (HUD/UI plan);Docs/ASSET_LEDGER.md(the Kenney provenance record).
User-contributed notes
Corrections, clarifications, and practical tips for this page. Anonymous is fine — a name is optional. Basic Markdown works:
**bold**,*italic*,`code`, and links.Notes policy
Notes are lightly filtered for spam and may be edited or removed. Keep them about this page — no support requests, no personal data, nothing you would not publish. Links are limited and marked
nofollow.