the symptom, in your words

"Editor vsync hides physics cost — time the step directly"

✓ verified on engine 26.07.15alane: Making it performposted

▸ SYMPTOM

You're profiling a physics-heavy scene in the editor and average fps pins at exactly 60.0 regardless of how many Rigidbodies, colliders, or raycast-wheel vehicles you add. Your performance metrics suggest everything is fine, even when the physics workload should clearly be saturating the CPU.

▸ CAUSE

Editor-embedded play mode is hard-capped at the desktop compositor's vsync (60 Hz). Neither fps_max 0 nor r_frame_sync_enable 0 lifts the cap — it's the OS windowed-present sync on the editor window, not the engine frame-sync. Frame-based fps measurement only tells you "the engine met every 60 Hz deadline," never how much headroom remains or where the real knee is.

▸ FIX

Implement IScenePhysicsEvents on a component and time the physics step directly with a Stopwatch:

snippet
public sealed class PhysicsStepTimer : Component, Component.IScenePhysicsEvents
{
    private readonly Stopwatch _sw = new();
    private readonly List<double> _samples = new();
    private const int WindowSize = 250; // ~5 s at 50 Hz

    void IScenePhysicsEvents.PrePhysicsStep()
    {
        _sw.Restart();
    }

    void IScenePhysicsEvents.PostPhysicsStep()
    {
        _sw.Stop();
        _samples.Add(_sw.Elapsed.TotalMilliseconds);

        if (_samples.Count >= WindowSize)
        {
            _samples.Sort();
            var avg = _samples.Average();
            var p99 = _samples[(int)(_samples.Count * 0.99)];
            Log.Info($"PhysicsStep: avg={avg:F2} ms, p99={p99:F2} ms ({_samples.Count} samples)");
            _samples.Clear();
        }
    }
}

The PrePhysicsStep/PostPhysicsStep callbacks bracket the real 50 Hz solver step. Log avg + worst-1% step milliseconds per window alongside your fps p1 — this is your true, vsync-free physics CPU cost.

Budget rule of thumb: each physics step has a 20 ms tick budget. A per-car cost of ~13 us/step (compound-folded Rigidbody) means 16 cars with 128 colliders each can sit at 0.39 ms avg / 0.78 ms p99 — under 4% of the tick. Physics part count and collider count are effectively free at roster scale; look for your binding cost elsewhere (world regen, triangle rebuild).

▸ WHY IT WORKS

Without this technique, you'll either (a) believe your physics is fine because avg fps = 60, or (b) try to optimize systems that aren't actually bound. Frame fps under vsync is a binary "met deadline / didn't" signal — the step timer gives you the continuous cost curve you need to find the real knee before it arrives.

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