"Sandbox.Decal scales to thousands — the bound is fill rate, not a hard cap"
▸ 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
- Enable a per-frame census overlay (live frame timing + decal count).
- Spam paint into one concentrated, overlapping area — the fill-rate worst case (maximum overdraw).
- Escalate the cap convar upward.
- 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.
- Published