From 7d685e8b46574f0925087f4652b8809dade1fe68 Mon Sep 17 00:00:00 2001 From: "nicogo.eth" Date: Wed, 7 Oct 2026 12:57:10 +0200 Subject: [PATCH] [Physics] fix: MaxStepPerFrame now bounds steps per frame, -1 no longer freezes the simulation The guard `stepCount < MaxStepPerFrame || MaxStepPerFrame != -1` did not depend on stepCount: any cap other than -1 never bounded the loop, and -1 never ran a step. Fixes #3522 Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Stride.BepuPhysics.Tests/BepuTests.cs | 15 +++++++++++++++ .../Stride.BepuPhysics/BepuSimulation.cs | 2 +- .../Stride.BepuPhysics/Module.cs | 1 + 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Tests/BepuTests.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Tests/BepuTests.cs index 32323b7cf..a7ab00eb0 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Tests/BepuTests.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics.Tests/BepuTests.cs @@ -591,6 +591,21 @@ namespace Stride.BepuPhysics.Tests RunGameTest(game); } + [Theory] + [InlineData(3, 10, 3)] + [InlineData(3, 2, 2)] + [InlineData(-1, 10, 10)] + public static void MaxStepPerFrameTest(int maxStepPerFrame, int pendingSteps, int expectedSteps) + { + using var simulation = new BepuSimulation { MaxStepPerFrame = maxStepPerFrame }; + int steps = 0; + simulation.Register(new SimUpdateListener { SimUpdate = () => steps++ }); + + simulation.Update(simulation.FixedTimeStep * pendingSteps); + + Assert.Equal(expectedSteps, steps); + } + private class SimUpdateListener : ScriptComponent, ISimulationUpdate { public Action? SimUpdate, AfterSimUpdate; diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs index ba193cd4a..86be60e4c 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs @@ -731,7 +731,7 @@ public sealed class BepuSimulation : IDisposable // TimeSpan multiplication is lossy, skipping mult when we can _remainingUpdateTime += TimeScale == 1f ? elapsed : elapsed * TimeScale; - for (int stepCount = 0; _remainingUpdateTime >= FixedTimeStep && (stepCount < MaxStepPerFrame || MaxStepPerFrame != -1); stepCount++, _remainingUpdateTime -= FixedTimeStep) + for (int stepCount = 0; _remainingUpdateTime >= FixedTimeStep && (MaxStepPerFrame == -1 || stepCount < MaxStepPerFrame); stepCount++, _remainingUpdateTime -= FixedTimeStep) { if (_softStartScheduled) { diff --git a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Module.cs b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Module.cs index 80f3a0b72..1e4717a9c 100644 --- a/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Module.cs +++ b/sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/Module.cs @@ -9,6 +9,7 @@ using Stride.Core.Reflection; [assembly: InternalsVisibleTo("Stride.BepuPhysics.Debug")] [assembly: InternalsVisibleTo("Stride.BepuPhysics.Navigation")] [assembly: InternalsVisibleTo("Stride.BepuPhysics.Soft")] +[assembly: InternalsVisibleTo("Stride.BepuPhysics.Tests")] namespace Stride.BepuPhysics;