Skip to content

The Time Manager

Lucky Engine's Time Manager is the conductor of the simulation. It decides what runs, when, and at what rate, so every physics engine, script callback and engine subsystem in a scene advances on one shared clock instead of fighting each other.

Each scene has its own Time Manager, and its settings are saved with the scene. You configure it in Edit → Settings, on the pages of the Scene group: Time Runners, Scripts, Subsystems, Physics, Recorder and Execution Order.

In short

The Time Manager runs up to eight fixed-rate clocks, called runners, on one shared timeline. Every simulation step runs five phases in a fixed order, and every script callback and engine system is bound to a runner and a phase. New scenes use a deterministic time mode, so a scene produces the same sequence of steps at any frame rate. The script-side view is in Time Runners.

What it does

  • Advances the simulation. Each rendered frame, the Time Manager runs zero or more simulation steps, depending on the time mode and on how much time has passed.
  • Coordinates multiple rates. Different systems often want different rates, for example control at 50 Hz and recording at 30 Hz. Each binds to a runner, and all runners share one timeline, so their steps line up wherever their rates allow.
  • Enforces phase order. Within a step, work is divided into five fixed phases, and the order is the same on every step.
  • Keeps the editor responsive. In the deterministic modes it limits how long the simulation may run in each rendered frame.

Time modes

Choose the mode with Scene Time Mode on the Time Runners page.

Mode How simulation time advances Suitable for
Sim Realtime (Deterministic Capped) (default) Steps every runner in order and never runs ahead of the wall clock. Under load it slows down rather than skipping steps. Interactive simulation, robotics, data collection
Sim High Perf. (Deterministic Uncapped) Steps every runner in order, as fast as the hardware allows, without tracking real time. Training, batch data generation, benchmarks
Game Realtime (Non-Deterministic) Follows the wall clock. When the machine cannot keep up, it skips simulation time to stay in sync. Games and demos where real-time pacing matters more than reproducibility

A deterministic mode produces the same sequence of steps regardless of frame rate, which is what makes reproducible simulation and data collection possible. In every mode, callbacks receive their runner's fixed step as ts: Game Realtime skips steps rather than stretching them.

The Time Runners page has three more settings:

Setting Default Description
Disable Viewport Rendering Off Shown only in Sim High Perf. Stops drawing the 3D viewport while the simulation runs, which greatly reduces GPU load when you only need data output.
Strict UI Responsiveness On In the two Sim modes, limits how long the simulation may run in each rendered frame so the editor keeps handling input and redrawing panels, at the cost of slightly lower throughput.
Strict UI Framerate 60 Shown while Strict UI Responsiveness is on. The editor frame rate to maintain, from 1 to 144.

Default runners

A runner is a fixed-rate clock. A new scene starts with two:

Runner Frequency Step Used by default
Robot (Runner 0) 50 Hz 20 ms Every script callback, subsystem, physics engine and recorder
Capture (Runner 1) 30 Hz 33.3 ms Nothing. Available for work that should run at a different rate, such as recording

The Time Runners page lists each runner as Runner N: Name, with an editable name, a frequency from 0.01 to 1000 Hz, and a remove button. To add a runner, click Add Time Runner: the new runner starts at 60 Hz and is named Runner 2, Runner 3 and so on. A scene can hold up to eight runners. At the limit the page shows Maximum of 8 Time Runners reached. The last remaining runner cannot be removed.

Runner timing stays exact over long runs, even for rates such as 30 Hz whose step does not divide evenly. Runners whose rates share a common beat therefore land on exactly the same instant every time they meet.

The SyncPoint runner

The Sync Point System is the one exception to the table above. By default it runs on a runner of its own, SyncPoint, at 10 Hz, which the scene adds when it starts playing. The Time Runners page lists it while the scene plays, and it counts towards the limit of eight, so a scene that uses it can have at most seven runners of its own. While you edit the scene the SyncPoint runner does not exist yet, so Sync Point TimeRunner and the Execution Order page show Robot in its place.

Binding systems to runners

Each script callback and engine system in the table below has a TimeRunner dropdown in Settings that chooses the runner that drives it. The list shows each runner with its rate, for example Robot [50.00Hz]. Binding a system to a runner with a different frequency changes how often it runs, with no code change, and the ts a callback receives always matches its runner's step.

Settings page Systems Fields
Scripts OnPreUpdate, OnUpdate, OnPhysicsUpdate, OnLateUpdate, OnPostUpdate Enable OnUpdate Scripts, OnUpdate Scripts TimeRunner, Execution Phase and Internal Phase Priority (one set per callback)
Subsystems External Action Gate, robot controllers, animation, audio scene, Sync Point System An enable checkbox, a TimeRunner dropdown, Execution Phase and Internal Phase Priority
Physics Box2D, Jolt, MuJoCo An enable checkbox and a TimeRunner dropdown. Their phases are fixed.
Recorder, General tab Data, video and lidar recorders MuJoCo Observer [Data] TimeRunner, MuJoCo Observer [Video] TimeRunner and Lidar Recorder TimeRunner, each with Execution Phase and Internal Phase Priority. Each appears once Record Data, Record Cameras or Record Lidar is on.

A recorder records at the rate of its runner. To record at 30 Hz while control runs at 50 Hz, point the recorder at Capture.

Phases

Every simulation step runs five phases in order. The Execution Phase dropdown lists them as Acquisition Phase [1st] to Export Phase [5th].

graph LR
    A([1. Acquisition]):::p1 --> B([2. Control]):::p2 --> C([3. Physics]):::p3 --> D([4. Validation]):::p4 --> E([5. Export]):::p5
    classDef p1 fill:#2d3a4f,stroke:#4a5e7e,color:#ffffff;
    classDef p2 fill:#2d4f3a,stroke:#4a7e5e,color:#ffffff;
    classDef p3 fill:#4f2d2d,stroke:#7e4a4a,color:#ffffff;
    classDef p4 fill:#4f4a2d,stroke:#7e764a,color:#ffffff;
    classDef p5 fill:#3a2d4f,stroke:#5e4a7e,color:#ffffff;

In a new scene, the phases hold the following:

Phase Purpose Runs by default Available, off by default
1. Acquisition Inputs and sensors Sync Point System, data recorder, video recorder OnPreUpdate, External Action Gate
2. Control Logic and AI OnUpdate, animation, robot controllers (motion graphs, IK and joint control)
3. Physics Physics engines Jolt, MuJoCo OnPhysicsUpdate, Box2D
4. Validation Checking and correcting the results of physics OnLateUpdate, lidar recorder
5. Export Rendering and output Camera capture for gRPC Step requests OnPostUpdate, audio scene

The recorders only do work while there is something to capture, and turning Record Data, Record Cameras or Record Lidar off on the Recorder page takes that recorder out of the step. Because the data and video recorders run in Acquisition, each recorded frame shows the state at the start of the step, before that step's control and physics. Jolt also does a small amount of work in each of the other phases; the Execution Order page lists it as Physics: Jolt Acquisition, Physics: Jolt Control and so on.

To move a script callback, subsystem or recorder to another phase, change its Execution Phase. Physics engines always run in their fixed phases.

Order within a phase

Within a phase, systems run in order of priority, lowest first, and the order repeats on every step. Script callbacks, subsystems, recorders and the Sync Point System take their priority from the Internal Phase Priority field under their Execution Phase, from -1000 to 1000. The physics engines have fixed priorities. Systems with the same priority keep a fixed order among themselves, and on a shared step the systems of every runner that fires are ordered together.

In a new scene:

  • The Sync Point System (priority -200) runs first in Acquisition, so TimeSync.IsAtSyncPoint is up to date for the rest of the step. Only the External Action Gate, when it is on, runs before it.
  • In Control, OnUpdate (-200) runs before animation (-100), and both run before the robot controllers (0), so the controllers use the targets that OnUpdate has just set.
  • The other script callbacks, subsystems and recorders start at 0.

To make one system run before another in the same phase, give it a lower Internal Phase Priority. Set priorities, phases and runners while you edit the scene: they take effect when it starts playing.

The Execution Order page shows the current configuration: every enabled system grouped under its phase and sorted by priority, with its priority in brackets, its name, and its runner and rate. Systems with the same priority may be listed in a different order from the one they run in. When free updates are on, Script: OnFreePreUpdate and Script: OnFreePostUpdate appear as the first and last headers.

What you can rely on

The Time Manager makes these guarantees about every simulation step:

  • All runners share one timeline. Simulation time advances by the same amount on every runner, every step. Runners cannot drift apart.
  • A runner fires only when its next step time is reached. On any other step it does nothing.
  • Phases always run in order. Acquisition first, Export last, every step, every scene.
  • At a shared step, callbacks interleave by phase. Never one whole runner after the other.
  • The order within a phase is the same on every step.
  • ts is always the runner's own step, in seconds, even on a shared step.

Shared steps

When the steps of two runners fall on the same instant, the Time Manager runs them together as one shared step. The phases still set the order: each phase runs the callbacks of every runner that fires before the next phase begins.

For the default Robot (50 Hz) and Capture (30 Hz) runners, the steps coincide every 100 ms, a rate of 10 Hz (the greatest common divisor of 50 and 30):

graph LR
    T0["0 ms<br/>Robot + Capture"]:::shared --> T1["20 ms<br/>Robot"] --> T2["33 ms<br/>Capture"] --> T3["40 ms<br/>Robot"] --> T4["60 ms<br/>Robot"] --> T5["67 ms<br/>Capture"] --> T6["80 ms<br/>Robot"] --> T7["100 ms<br/>Robot + Capture"]:::shared
    classDef shared fill:#f5503d,stroke:#f5503d,color:#ffffff;

This 10 Hz beat is the natural rate for the Sync Point System, and the rate it uses by default.

A familiar analogy

Multi-rate runners on one timeline behave like synchronised clock domains in digital hardware: clocks derived from one base clock realign at their common beat. A multi-rate fixed-step solver, such as Simulink, schedules different sample rates on a shared base step in the same way.

Sync points

The Sync Point System marks steps where the control and recording rates line up, so a script can start an episode on a step that both see. Enable Sync Point System, on the Subsystems page, is on by default. Turn it off only if you do not need resets and recordings to line up, for example in a scene you only edit.

Each time the Sync Point System's runner fires is one sync point, so the runner's rate is the sync rate. By default that runner is SyncPoint, at 10 Hz, which the scene adds when it starts playing (see The SyncPoint runner). The scene keeps this default when you save it and open it again.

To use another rate, choose a runner in Sync Point TimeRunner: Robot, for example, makes every 50 Hz step a sync point. For a rate that no runner has, add a runner at that rate on the Time Runners page first, then select it.

A scene saved with an earlier release of Lucky Engine keeps the runner it was saved with, which is Robot unless another was chosen: every 50 Hz step is then a sync point. The bundled Welcome scene works this way. For a 10 Hz beat in such a scene, add a 10 Hz runner on the Time Runners page, select it in Sync Point TimeRunner and save the scene.

To check the rate in use, log TimeSync.SyncHz once Play has started. While the scene plays, Sync Point TimeRunner also shows the runner in use.

Scripts read sync points through the static TimeSync class:

Member Description
TimeSync.IsAtSyncPoint true during the step on which a sync point fires
TimeSync.CurrentSyncIndex Increases by one at each sync point. Save it and wait for it to change.
TimeSync.SyncHz The sync rate in hertz, or 0 when the Sync Point System is off

A typical use is to wait for TimeSync.IsAtSyncPoint before resetting the scene and calling Observer.BeginEpisode(), so the start of the behaviour and the first recorded frame land on the same step. Deterministic recording with sync points shows the full pattern.

Physics rate

Box2D, Jolt and MuJoCo are each bound to a runner on the Physics page (Box 2D TimeRunner, Jolt TimeRunner, MuJoCo TimeRunner). MuJoCo has extra settings that decide how many physics steps it takes per runner step:

Field Default Description
Perf Mode Performance: Substeps How MuJoCo steps fit into the phases. See the table below.
Substeps 10 Solver substeps per runner step, from 1 to 100. Used only in Performance: Substeps.
Base Runner Hz Read-only. The rate of the MuJoCo runner.
Effective Physics Hz Read-only. The resulting physics rate: the runner rate, multiplied by Substeps in Performance: Substeps.
XML Timestep Read-only. The MuJoCo timestep in seconds, one divided by the effective rate.
Perf Mode Behaviour
Quality: Phase Separated Splits each MuJoCo step across the phases, so other engine updates take effect between its parts. One MuJoCo step per runner step.
Balance: True Steps Runs each MuJoCo step as one unit in the Physics phase. One MuJoCo step per runner step.
Performance: Substeps Runs Substeps MuJoCo steps in a row in the Physics phase, without interruption. Suits control and validation logic that can run slower than physics, such as a 50 Hz controller on 500 Hz physics.

With the defaults, MuJoCo runs on the 50 Hz Robot runner with 10 substeps: 500 Hz physics with a timestep of 0.002 s. When the scene plays, this timestep replaces the timestep set in a robot's MJCF file.

Free updates

OnFreePreUpdate and OnFreePostUpdate sit outside the simulation clock. OnFreePreUpdate runs once per rendered frame before that frame's simulation steps, and OnFreePostUpdate runs after them. Their ts is the time since the last rendered frame, not a fixed step.

Free updates are off by default. Turn them on with Enable Free Step Update Scripts on the Scripts page. As its tooltip says, they are not recommended for simulations: a frame can run zero, one or several simulation steps, so free updates are not in step with the simulation. Use them for work that belongs on the display refresh rate, such as UI bookkeeping or reading one-frame key presses (see Input).

Where to go next

  • Time Runners describes the same system from a script's point of view.
  • Entity Lifecycle lists which callbacks run in which phase by default.
  • Settings covers every page of the Settings panel.
  • Recording explains the recorders and how their rates are set.
  • gRPC API explains how the External Action Gate keeps the simulation in lock-step with an external client.