the symptom, in your words

"Sandbox.Decal scales to thousands — the bound is fill rate, not a hard cap"

✓ verified on 26.07.18
lane Writing gameplayposted

▸ SYMPTOM

You're building a persistent decal system — paint, impacts, splatter — and you want to know how many Sandbox.Decal instances you can keep alive before something breaks. There's no documented cap, and you're worried about a crash cliff as the count climbs.

▸ CAUSE

Sandbox.Decal instances are lightweight scene objects: one box projection each, frustum-culled, with no per-frame allocation while static. There is no hard engine cap and no crash cliff — a persistent-paint system can hold thousands. The performance envelope has three practical bounds, all gradual:

  • Scene-object count — tolerant to many thousands.
  • App-side bookkeeping — an O(n) ring-sweep per spawn stays trivial at thousands.
  • Fill rate for the on-screen subset — the real ceiling, and it's worst when many decals overlap the same pixels (overdraw).

Push past the practical ceiling and you get a gradual frame-time sag, never a crash.

▸ FIX

Give the persistent system a convar-backed ring buffer: cap the live count, recycle the oldest (brief fade, then destroy). Putting the cap behind a convar lets you dial it per machine — weak GPU vs strong — with no recompile.

Keep the visual derivation a pure function of replicated inputs so a peer running a different cap still renders the same marks; it just recycles at a different count. This keeps networked clients visually consistent even when their local caps differ.

Bench method to find a machine's ceiling

  1. Enable a per-frame census overlay (live frame timing + decal count).
  2. Spam paint into one concentrated, overlapping area — the fill-rate worst case (maximum overdraw).
  3. Escalate the cap convar upward.
  4. Back off to a margin below where the 1% lows start to sag.

Measured on one mid-2026 desktop (60 Hz, live frame census): an 8192-decal cap held a locked 60 fps with 1% lows in the mid-50s, while a 16384 cap sagged to a ~56 fps 5-second average with 1% lows into the teens and p99 frame spikes near 50 ms. 8192 shipped as that project's default.

▸ WHY IT WORKS

Because decals are cheap static scene objects, the count itself isn't the constraint — the constraint is how many overlapping decals the GPU has to shade per frame. A ring buffer bounds the worst case deterministically, and a convar lets each machine sit just below its own fill-rate ceiling. Note those numbers are hardware-relative (one desk, one GPU); what's portable is the shape: the failure mode is gradual frame-time sag (not a cliff), the bench method above, and the fact that the practical ceiling on a mid-2026 desktop sits in the several-thousands, not the hundreds.

Verified on engine 26.07.18: 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