"My s&box UI won't update / is frozen"
You change game state, but the panel on screen never reflects it. A counter stays stale, a modal won't close, a loading bar sits at 0% even though work is progressing. No error, no crash — the UI is simply not re-rendering.
BuildHash() is the only re-render trigger in s&box Razor. The panel recomputes the hash each frame and re-renders only when that integer changes. Any value your markup reads but the hash omits is silently frozen.
Hashing a collection reference or object identity stays constant even as contents mutate — so the UI looks dead while data moves underneath.
Hash everything the markup reads — collection contents, derived display strings (they're cheap), day/date, and every open/closed flag you branch on:
@inherits PanelComponent
@code {
protected override int BuildHash() => HashCode.Combine(
Counts.Values.Sum(), // collection CONTENTS, not the reference
Game?.Day ?? 0,
Progress, // loading bar / time-sliced work
UIState.ThisPanelOpen,
UIState.AnyModalOpen
);
// HashCode.Combine nests for >8 values if needed
}Skeleton reminder:
@using Sandbox
@using Sandbox.UI
@namespace YourGame
@inherits PanelComponent
<root>
@if (Game is not null && UIState.ThisPanelOpen)
{
<div class="panel">@Counts.Values.Sum()</div>
}
</root>If the panel type itself can't be found from C#, fix @namespace first — razor-namespace-trap.
Razor does not diff the full tree every frame. It treats BuildHash() as a dirty bit: same int → skip rebuild. Content-sensitive hashes (sums, counts, flags, progress) change when the player-visible state changes, so the markup runs again and the screen catches up.