s&box/field-guide
the symptom, in your words

"Building an s&box HUD"

✓ verified on engine 26.07lane: Building UIposted
▸ SYMPTOM

You need a real HUD: prompts, hotbar, toasts, title/start menu, pause — without input races, unpausable modals, or frozen panels.

▸ CAUSE

s&box UI is Razor PanelComponents on a ScreenPanel. Without a single modal router and correct BuildHash, panels fight over keys and go stale. SCSS is a flex-default dialect with its own pointer-events rules.

▸ FIX

Host

One GameObject hosts ScreenPanel + every PanelComponent. Paired MyPanel.razor.scssroot selector is the class name (MyPanel { … }).

Modal routing — static UIState

snippet
public static class UIState
{
    public static bool StartMenuOpen = true;
    public static bool PauseOpen;
    public static bool InventoryOpen;
    // …

    public static bool AnyModalOpen =>
        StartMenuOpen || PauseOpen || InventoryOpen /* + day-summary, etc. */;
}

Game clock and player input check AnyModalOpen → opening any panel pauses the world.

Each panel closes itself on e.g. Input.Pressed("attack2") in its own OnUpdate. Toggle-key ownership lives in ONE place per key — two components both reacting to the same press = same-frame open/close races.

Fold every open flag into BuildHash() (ui-frozen-buildhash).

Layout patterns that work

RegionRole
Top-leftDashboard / resources
Top-rightToast stack (TimeSince + prune in OnUpdate)
Bottom-centerPrompt + hotbar
Bottom-rightHints
Full-screenModal backdrops inside the same panel tree

Title screen: default StartMenuOpen = true; game-state waits; buttons call StartNew() / ContinueGame(). Continue only when FileSystem.Data.FileExists(saveFile). Destructive New Game → inline confirm, not a second modal.

Admin/debug panel (toggle key): poke singletons directly — time/weather, grant, spawn, teleport. Build it early.

SCSS dialect notes

  • Everything is a flex container by default — set flex-direction: column on stacks.
  • HUD root: position: absolute; pointer-events: none;pointer-events: all only on clickable children (also makes the cursor usable over them).
  • Nesting + &.modifier, linear-gradient, transition + &:hover, text-shadow over 3D.
  • Event handlers: onclick=@Close or onclick=@(() => Do(x)). Loop-variable capture: copy to a local (var idx = slot++;) before the lambda.

Namespace

@namespace YourGame on every razor — razor-namespace-trap.

Edit .razor with byte-safe tools only — PowerShell 5.1 mojibake destroys non-ASCII (emoji icons, etc.).

▸ WHY IT WORKS

A single AnyModalOpen gate gives gameplay one pause signal. One owner per toggle key eliminates open/close races. Hashing UIState flags keeps Razor in sync with the router. Flex + pointer-events defaults match how the engine's UI tree actually hit-tests.

Verified on engine 26.07 — seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?