#performance
15 items (2 guides · 13 fixes)
The practical patterns for Sandbox.Decal: orienting the projection box to a hit normal, keeping a decal byte-identical across multiplayer peers, making paint follow an animated character, and scaling to thousands of decals without a performance cliff, plus the pitfalls that catch each one.
A disciplined method for answering "the game feels slow" in an s&box project without shipping speculative optimizations: environmental pre-checks, measurement tooling, an isolation matrix, and a verdict discipline that prevents wasted work.
Census tris × instances before decimating; prefer BoxCollider over ModelCollider for decorative props.
At single-digit sun elevation a street canyon gets no direct ground light, so nothing on the ground casts a visible shadow while the cascade is billed in full; fix it with sun azimuth, not renderer flags.
For a chunked runtime-mesh generator, REGEN (single-threaded generation CPU) is the binding resource, not fps or draw calls. Quartering the chunk count produces zero regen change; the lever is threading the passes.
BoxCollider is cheap and follows WorldScale; Capsule/ModelCollider don't scale with WorldScale and ModelCollider is costly on clutter.
Census tris × instances first: silhouette fails before UVs; inject usemtl after scripted OBJ export.
ModelRenderer.CastShadows doesn't exist as a settable property: use renderer.SceneObject.Flags.CastShadows instead.
Editor-embedded play mode pins at exactly 60 fps regardless of cvars. The cap is the compositor's vsync on the editor window, not the engine frame sync. Under the cap even the p50 pins to 16.67 ms: read the tail (p99, worst frame, over-budget count), not the median.
Frame fps pins at 60 in the editor regardless of physics load: implement IScenePhysicsEvents.PrePhysicsStep/PostPhysicsStep with a Stopwatch to get the true, vsync-free CPU cost of the solver step.
Sandbox.Diagnostics.FrameStats and PerformanceStats are whitelist-clean and reachable from game-assembly components: build a relative perf probe without any engine benchmark API. Raw GC.GetAllocatedBytesForCurrentThread/GC.CollectionCount are SB1000-blocked in-editor; use PerformanceStats instead.
GameTask.RunInThreadAsync for parse/math; main thread only for engine objects; Yield every N items for loading UI.
A loading screen mounted right before a synchronous multi-second call never paints: one consumed tick isn't enough; gate the block on a frame counter (~6 frames) so the Razor pipeline finishes mount → style → layout → paint.
Sandbox.Decal instances are lightweight scene objects with no hard engine cap, so a persistent-paint system can hold thousands. The failure mode is a gradual frame-time sag driven by fill-rate/overdraw, never a crash cliff. Bound it with a convar-backed ring buffer and bench for your target hardware.
Sandbox.Json.Serialize always emits pretty-printed JSON and exposes no public option to disable it: every network payload pays a whitespace tax. Re-drive the same serializer through a compact writer for a 41–56% size cut.