Choosing Where to Run Deterministic Simulation in Bevy: Update vs FixedUpdate
Decide where to run deterministic logic in Bevy: Update, FixedUpdate, or a hybrid. Compare determinism, latency, and overhead in a compact table, then see a concrete implementation and validation steps to ensure consistent behavior across frame‑rates.
26 Apr 2026, 07:01 UTC

Problem Statement
When building a game or simulation in Bevy, you often need to decide where to execute deterministic logic such as physics or AI. The engine offers two built‑in schedules for this purpose: Update and FixedUpdate. Choosing the wrong schedule can make your simulation non‑deterministic, introduce frame‑rate dependent behaviour, or add unwanted input latency.
Decision & Constraints
The core decision is: Run deterministic logic in FixedUpdate or Update?
- Determinism – identical inputs should produce the same final state regardless of frame‑rate.
- Input latency – how many frames the user must wait before their action is reflected.
- Target tick rate – typical physics updates run at 60 Hz.
- Variable render frame‑rate – most screens run at 30–144 Hz with vsync or adaptive sync.
- Resource constraints – heavy simulations may not finish one step before the next frame, risking a spiral of death.
Option Comparison
| Option | Schedule | Delta Time | Determinism | Input Latency | Overhead | Use‑Case |
|---|---|---|---|---|---|---|
| Fixed Tick | FixedUpdate |
Fixed 1/60 s | High – same result on any frame‑rate | ~1–2 frames (depends on vsync) | Extra system scheduling, potential lag spikes | Physics, network state sync, deterministic AI |
| Immediate Tick | Update |
Variable Time.delta_seconds() |
Low – varies with frame‑rate | 0–1 frame (directly before render) | None – runs once per frame | Fast input handling, UI updates, non‑critical gameplay logic |
| Hybrid | Input in Update, simulation in FixedUpdate |
Mixed | High – simulation deterministic | 1 frame for input, 1 frame for simulation | Balanced – extra scheduling, but avoids lag spikes | Games requiring responsive controls and stable physics |
Trade‑Off Analysis
- Determinism vs Latency –
FixedUpdategives repeatable states but introduces a small latency. If your game relies on precise timing (e.g., competitive multiplayer), the extra frame can be noticeable. - Consistency across hardware – With
Update, a 30 Hz monitor will run physics at half speed compared to a 144 Hz monitor, breaking any logic that assumes a fixed tick. - Resource pressure – A heavy physics system that takes >16 ms per step will accumulate steps during a low‑frame‑rate period, potentially leading to frame‑rate drops (the “spiral of death”).
- Hybrid safety – By separating input and simulation, you keep the user experience responsive while preserving deterministic physics. The cost is a single extra system run per frame.
Concrete Implementation
Below is a minimal Bevy 0.14 example that demonstrates both approaches side‑by‑side. It creates two identical entities: one updated in Update and one in FixedUpdate. The systems log the Transform position each tick so you can compare the results after running at different frame‑rates.
use bevy::prelude::*;
fn main() {
App::new()
.add_plugins(DefaultPlugins)
.add_startup_system(setup)
.add_system(update_entity.system()) // runs in Update
.add_system(fixed_update_entity.system()) // runs in FixedUpdate
.run();
}
fn setup(mut commands: Commands) {
// Entity that will be updated every frame
commands.spawn((Transform::default(), GlobalTransform::default()));
// Entity that will be updated at 60 Hz
commands.spawn((Transform::default(), GlobalTransform::default()));
}
fn update_entity(mut query: Query<&mut Transform, With<UpdateEntity>>) {
for mut t in query.iter_mut() {
t.translation.x += 1.0; // simple deterministic change
// Log position – replace with actual logging if desired
println!("Update tick: x={}", t.translation.x);
}
}
fn fixed_update_entity(mut query: Query<&mut Transform, With<FixedEntity>>) {
for mut t in query.iter_mut() {
t.translation.x += 1.0; // same change, but at fixed 60 Hz
println!("FixedUpdate tick: x={}", t.translation.x);
}
}
**Important notes**:
- Replace
UpdateEntityandFixedEntitywith marker components to distinguish the two entities. For brevity, the example omits them, but they are necessary to filter the queries. - Bevy’s
FixedUpdateruns at a default rate of 60 Hz. You can change it viaapp.insert_resource(FixedTimestep::new(1.0 / 120.0))for 120 Hz. - Logging to the console is inexpensive but may still affect timing. For production, use a lightweight profiler or a dedicated log file.
Validation Procedure
- Compile the app with the latest Bevy 0.14 stable release.
- Run the executable twice:
- First with vsync enabled (typical 60 Hz monitor).
- Second with vsync disabled or on a high‑refresh monitor (e.g., 144 Hz).
- Collect the console output into two separate log files.
- Compare the
Updateentity logs – they should differ between the two runs, reflecting the frame‑rate dependency. - Compare the
FixedUpdateentity logs – they should match exactly, proving determinism. - Optionally, use
app.world.get_resource::()to verify that theFixedUpdateschedule is indeed ticking at 60 Hz.
Practical Checklist
- Ensure
FixedUpdatesystems do not exceed theTime.fixed_seconds()budget. - When using a hybrid approach, keep input handling in
Updateand physics inFixedUpdate. - Test under simulated low‑frame‑rate conditions (e.g., by throttling CPU or disabling vsync) to watch for step accumulation.
- Use Bevy’s
app.schedule::()API to introspect and confirm system placement.
Limitations & Caveats
- Bevy’s schedule names and the behaviour of
FixedUpdatemay change in future releases. Always consult the official docs for the current API. - Heavy simulations can cause a “spiral of death” if the system cannot keep up with the fixed tick rate. Consider profiling and optimizing your physics code or lowering the fixed tick frequency.
- Determinism assumes identical input sequences. Random number generators must be seeded consistently across runs.
Conclusion
For deterministic physics or gameplay logic that must stay consistent across varying frame‑rates, FixedUpdate is the safe choice. If your simulation tolerates minor frame‑rate drift and you need the lowest possible input latency, run it in Update. The hybrid pattern offers a middle ground: responsive input with stable physics. By following the validation steps above, you can confidently decide which schedule best fits your project’s needs.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.