Skip to content

RFC: Add a physics fixed-step catch-up cap #3012

Description

@luzhuang

RFC: Physics fixed-step catch-up cap

Problem

PhysicsScene._update(deltaTime) currently consumes all accumulated fixed steps in a single engine frame:

const simulateTime = this._restTime + deltaTime;
const step = Math.floor(simulateTime / fixedTimeStep);
this._restTime = simulateTime - step * fixedTimeStep;

This is mathematically valid for simulation catch-up, but it has no public cap. When frame time fluctuates around fixedTimeStep, gameplay code that writes forces or velocity in onPhysicsUpdate() can observe uneven render-frame motion such as:

0 physics step in one render frame
2 physics steps in the next render frame

The issue is not specific to migration. Any Galacean project that drives gameplay through onPhysicsUpdate() can hit the same tradeoff between simulation catch-up and render-frame smoothness.

External references

Unity exposes this concept through Time.fixedDeltaTime plus Time.maximumDeltaTime / Project Settings Maximum Allowed Timestep.

Unity's docs describe that:

  • fixed updates can backlog when frame rate is lower than the fixed timestep rate
  • Unity executes multiple fixed updates to catch up
  • there is a maximum timestep period beyond which Unity does not keep catching up
  • Maximum Allowed Timestep caps the worst-case time spent on physics and FixedUpdate in a low-frame-rate frame

Cocos Creator 3D also exposes the concept directly as PhysicsSystem.maxSubSteps; in Cocos Creator 3.8.8 the default is 1.

Proposed engine capability

Add a public physics catch-up cap to Galacean, for example:

class PhysicsScene {
  fixedTimeStep: number;
  maxSubSteps: number;
}

Suggested semantics:

const rawStep = Math.floor((restTime + deltaTime) / fixedTimeStep);
const step = Math.min(rawStep, maxSubSteps);
restTime = simulateTime - step * fixedTimeStep;

Open default-value question:

  • Infinity preserves current Galacean behavior and makes this a non-breaking API addition.
  • Math.floor(engine.time.maximumDeltaTime / fixedTimeStep) aligns with Unity's maximumDeltaTime style, but changes behavior if applied by default.
  • 1 matches Cocos Creator's default, but would be a behavioral change for native Galacean projects.

Conservative recommendation: add maxSubSteps as engine API with a non-breaking default first, then let migration/runtime layers explicitly set it to Cocos defaults when needed.

Non-goals

  • Do not solve render interpolation in this RFC.
  • Do not change Engine.targetFrameRate or vSync behavior in the same change.
  • Do not silently change native Galacean projects' physics catch-up behavior without a compatibility decision.

Validation plan

Add focused engine tests for:

  • maxSubSteps = Infinity preserves current multi-step catch-up behavior.
  • maxSubSteps = 1 executes at most one onPhysicsUpdate() and one native physics update per engine update.
  • accumulated restTime is preserved when the cap prevents full catch-up.
  • maxSubSteps = 0 disables fixed-step simulation while preserving accumulated time, or is rejected/clamped depending on the chosen API contract.

Migration relevance

For migrated Cocos projects, the migration compat layer can set scene.physics.maxSubSteps = 1 to match Cocos 3D default behavior. That should be explicit migration policy, not an implicit engine default forced onto all Galacean projects.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions