Skip to content
Edit on GitHub

The runtime UI

verifiedAgainst 9a2d070 · verifiedOn 2026-09-10 · the automated staleness banner is planned, not built.

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.

In-game hand-specimen assay interface 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.

In-game contextual reticle and gameplay HUD Figure 2: The code-bootstrapped UGUI HUD: reticle, hotbar, and ContextualTargetHudRuntime targeting the placed crucible apparatus.

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
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.)

  1. 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.
  2. Runtime is thin; logic is pure. Any branch that chooses text, a number, or a state goes in a static class with no MonoBehaviour and gets an EditMode test. The *Runtime only owns Image/Text objects and forwards.
  3. Diff before you draw. Cache the last-drawn value and repaint only on change. Allocating widgets, formatting strings, or setting .text unconditionally every frame is the mistake this codebase’s HUD avoids everywhere.
  4. The HUD never invents scientific prose. Player-facing scientific text comes from a narrator with a committed ScienceEvent source; FormatAccount throws InvalidOperationException on a line with SourceEventId == 0 rather than show it. UI presents evidence, it does not author it (Docs/CHEMISTRY_FOUNDATIONS.md — the after-action evidence boundary).
  5. 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 *Runtime directly. Update Docs/ASSET_LEDGER.md for any new third-party asset.
  6. Diagnostics guard on Debug.isDebugBuild. Anything developer-only (DebugOverlayRuntime) must return early from Create() in a release build.
  • Upstream: everything. VoxelSandbox.UI’s asmdef references Core, Chemistry, Data, Industry, Inventory, Player, World, and Unity.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 — the asmdef now includes it, for VesselRunViewRuntime. 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 (in VoxelSandbox.Player), not a private InputAction.
  • Tests: Assets/_Game/Tests/EditMode/UI/VoxelSandbox.UI.Tests.asmdef references only Chemistry, Inventory, UI (+ TestAssemblies) — the pure layer is reachable without the rest of the graph.
  • 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 / BlockDefinition lookups 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.

  1. Loading notes…