the symptom, in your words

"A runtime-written Data-filesystem image needs a code Texture, not a CSS background-image URL"

✓ verified on 26.07.15a
lane Building UIposted

▸ SYMPTOM

You write an image to the user Data filesystem at runtime — a per-save thumbnail, an in-game screenshot, a generated minimap — and then try to show it in a razor panel the obvious way:

snippet
.thumb {
  background-image: url("thumbnails/save_01.png");
}

Nothing renders. No exception, no red error — the panel just stays blank. The same URL scheme works fine for images that ship in your content, which makes it look like the file wasn't written, so you go chasing a phantom write bug.

▸ CAUSE

A CSS url(...) in s&box UI resolves against the mounted content/asset filesystems, not the user Data filesystem (FileSystem.Data). A path that only exists under Data FS therefore never resolves from CSS, and the miss is silent — the property is simply dropped.

The write almost certainly succeeded; the addressing is what's wrong. CSS has no handle on the Data FS at all.

▸ FIX

Turn the image into a Texture in code and assign it to Style.BackgroundImage. The proven, whitelist-clean recipe is to decode your own pixels and push them into a dynamic texture:

snippet
var tex = Texture.Create( width, height )
    .WithFormat( ImageFormat.RGBA8888 )
    .WithDynamicUsage()
    .Finish();

tex.Update( pixels ); // pixels is a Color32[]

panel.Style.BackgroundImage = tex;

This is the same shape used to build a live minimap texture from code, and it works because you are handing the UI an in-memory Texture object rather than asking CSS to resolve a filesystem path.

Two things that bite alongside this:

  • Don't @ref a collection of stateful child panels for a @foreach of cards. Refs into a dynamically-built list get fragile fast. Use a small custom Panel subclass driven by markup attributes that it reads in its own Tick — each card owns its state instead of the parent juggling refs.
  • Data FS's clean write surface is text-only. The whitelist-friendly path is WriteAllText. If you want a thumbnail without touching a binary/PNG API, store a palette-indexed text thumbnail — one character per cell. At 64×64 that's about 4 KB, trivially written and read back, and you rebuild the Color32[] from it at load time.

▸ WHY IT WORKS

The UI resolves background-image: url(...) through the asset/content mount points, which are read-only, compiled, and known at build time — exactly what CSS expects. The user Data FS is a runtime, per-machine write target with no CSS binding. By decoding the bytes yourself and creating a Texture, you skip the resolver entirely and give the renderer something it can draw directly, which is also why the dynamic-usage minimap path is the reliable pattern for any image you generate at runtime.

Verified on engine 26.07.15a: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord