stride3d/stride · error · ArgumentException

Duration provided must be greater than zero

Error message

Duration provided must be greater than zero

What it means

The FixedTimeStep property setter on BepuSimulation validates that the configured physics tick duration is strictly positive (value.Ticks > 0). A zero or negative TimeSpan would cause the simulation loop to spin or divide by zero when computing tick counts, so the setter rejects it with ArgumentException before mutating state.

Solutions

  1. Pass a strictly positive TimeSpan, e.g. sim.FixedTimeStep = TimeSpan.FromSeconds(1.0 / 60.0);
  2. Validate the configured value before assignment: if (ticks <= 0) throw/log before touching the property.
  3. Fix the source of the config value (settings file, YAML/JSON) so it contains a positive duration.

Example fix

// before
sim.FixedTimeStep = TimeSpan.Zero;
// after
sim.FixedTimeStep = TimeSpan.FromSeconds(1.0 / 60.0);
Defensive patterns

Strategy: validation

Validate before calling

if (timeSpan != null && timeSpan.Ticks > 0) {
    sim.FixedTimeStep = timeSpan;
} else {
    throw new ArgumentException("FixedTimeStep must be positive");
}

Try / catch

try { sim.FixedTimeStep = configured; } catch (ArgumentException ex) { log.Error("Invalid fixed time step", ex); sim.FixedTimeStep = TimeSpan.FromSeconds(1.0/60.0); }

Prevention

When it happens

Trigger: Assigning BepuSimulation.FixedTimeStep to TimeSpan.Zero, a negative TimeSpan (e.g. TimeSpan.FromSeconds(-0.016)), or an uninitialized TimeSpan field that was never set.

Common situations: Config-driven simulation setup where the fixed timestep comes from a settings file that was never filled in, or a default(TimeSpan) field passed through; also typos like TimeSpan.FromSeconds(-1/60).

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/98ebf5f9b66dcc3a. Report an issue: GitHub.

Appendix: source

Thrown at sources/engine/Stride.BepuPhysics/Stride.BepuPhysics/BepuSimulation.cs:258

    /// Multiplier over <see cref="SolverSubStep"/> during Soft Start
    /// </summary>
    [Display(13, "SoftStart Substep factor", CategoryConstraints)]
    public int SoftStartSubstepFactor { get; set; } = 4;

    /// <summary>
    /// The amount of time between individual simulation steps/ticks, by default ~16.67 milliseconds which would run 60 ticks per second
    /// </summary>
    /// <remarks>
    /// Larger values improve performance at the cost of stability and precision.
    /// </remarks>
    [DataMemberIgnore]
    public TimeSpan FixedTimeStep
    {
        get => _fixedTimeStep;
        set
        {
            if (value.Ticks <= 0)
                throw new ArgumentException("Duration provided must be greater than zero");
            _fixedTimeStep = value;
        }
    }

    #region UglyEditorWorkaroundForMasks
    // This nonsense is temporary, we need a specific editor UI for this
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer0 { get => CollisionMatrix.Get(CollisionLayer.Layer0); set => CollisionMatrix.Set(CollisionLayer.Layer0, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer1 { get => CollisionMatrix.Get(CollisionLayer.Layer1); set => CollisionMatrix.Set(CollisionLayer.Layer1, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer2 { get => CollisionMatrix.Get(CollisionLayer.Layer2); set => CollisionMatrix.Set(CollisionLayer.Layer2, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer3 { get => CollisionMatrix.Get(CollisionLayer.Layer3); set => CollisionMatrix.Set(CollisionLayer.Layer3, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer4 { get => CollisionMatrix.Get(CollisionLayer.Layer4); set => CollisionMatrix.Set(CollisionLayer.Layer4, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer5 { get => CollisionMatrix.Get(CollisionLayer.Layer5); set => CollisionMatrix.Set(CollisionLayer.Layer5, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer6 { get => CollisionMatrix.Get(CollisionLayer.Layer6); set => CollisionMatrix.Set(CollisionLayer.Layer6, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer7 { get => CollisionMatrix.Get(CollisionLayer.Layer7); set => CollisionMatrix.Set(CollisionLayer.Layer7, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer8 { get => CollisionMatrix.Get(CollisionLayer.Layer8); set => CollisionMatrix.Set(CollisionLayer.Layer8, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer9 { get => CollisionMatrix.Get(CollisionLayer.Layer9); set => CollisionMatrix.Set(CollisionLayer.Layer9, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer10 { get => CollisionMatrix.Get(CollisionLayer.Layer10); set => CollisionMatrix.Set(CollisionLayer.Layer10, value); }
    [DefaultValue(CollisionMask.Everything), Display(category:MaskCategory)] public CollisionMask Layer11 { get => CollisionMatrix.Get(CollisionLayer.Layer11); set => CollisionMatrix.Set(CollisionLayer.Layer11, value); }

View on GitHub (pinned to 96fad776d2)