- Steps per period
- 100
- Per-step energy factor
- 1.00395
- E/E0 after 10 periods
- 51.4
- Predicted
- 51.4
Real physics engines typically step at Δt = 1–4 ms (Gazebo's default maximum step size is 1 ms; PX4's Gazebo worlds use 4 ms): small enough that integrator error is invisible for a drone, cheap enough to run faster than real time.
Oscillator: x = −ω²x, ω = 2π rad/s (period T = 1 s), x0 = 1 m, v0 = 0, energy per unit mass E = ½v² + ½ω²x²; the trajectory is computed directly for n = N T/Δt steps. Attitude loop: θ = u2/I, I = 0.01 kg·m², PD controller u2 = I(Kp(θref − θ) − Kdθ) with Kp = ωa² = 625 s⁻², Kd = 2ζaωa = 25 s⁻¹ (ωa = 25 rad/s, ζa = 0.5), updated at 250 Hz and held between updates. The controller can only see states the physics engine produces, so when Δt > 4 ms it runs once per physics step (lockstep). Rotor limits are ignored here. Stability is judged over 4 s: an error that still grows between 2–3 s and 3–4 s is flagged.
Try this
- Explicit Euler at Δt = 10 ms gains energy every step. Check the readout: is E/E0 after 10 periods equal to (1 + ω²Δt²)n with n = 1000? Halve Δt: why does the growth fall from ×51 to about ×7?
- Switch to Semi-implicit Euler at the same Δt. The energy now wobbles but never drifts. It costs the same as explicit Euler, so why do physics engines (ODE, Bullet, MuJoCo's default) use it?
- With explicit Euler, raise Δt until the attitude loop reads Simulation unstable. Which integrator survives the largest step? The real 250 Hz loop is stable at every step size, so what exactly is failing?
- In the real-time factor calculator, find the rendering cost that drops RTF below 1. What does Headless buy you, and why is RTF > 1 useful for test suites and learning-based controllers?
- GPS-only RMS
- –
- Dead reckoning RMS
- –
- Fused RMS
- –
- Bias estimate
- –
RMS over the last 20 s, against the simulator's ground truth. On a real flight there is no ground truth, so these numbers cannot be measured.
Sensor models: accelerometer am = a + b + na (1 kHz, already rotated into the world frame with gravity removed), where b = turn-on bias (slider, fixed direction) + a slow drift (random walk 0.003 m/s²/√s, mean-reverting over 200 s) and na is white noise with σa; GPS pGPS = p + np, white noise σp per axis, sampled at the GPS rate and delivered L late. Ground truth: x = 20 sin(0.2t), y = 10 sin(0.4t) m (speed 2.8–5.7 m/s). Dead reckoning: v ← v + amΔt, p ← p + vΔt from the last reset. Fused: linear Kalman filter per axis with state (p, v, b); it predicts with am − b at 1 kHz and corrects with each fix, x ← x + K(pGPS − p), with the Kalman gain K computed from R = (assumed GPS σ)². With latency compensation it rewinds to the fix's timestamp, corrects, and re-predicts to now. GPS-only holds the latest fix.
Try this
- Let it run for 20 s and compare the three RMS readouts. In the first seconds dead reckoning can look best: its error grows as ½b t², so how far should a 0.05 m/s² bias take it in 20 s? Why can you check this in simulation but never on a real flight?
- Press GPS dropout (5 s) (or D). Watch the GPS-only marker freeze while the drone flies on. How far does the fused estimate drift without GPS, and how quickly does it recover?
- Set the latency to 500 ms, then switch Compensate GPS latency off. What happens to the fused RMS, and why? (PX4's EKF2 fuses GPS on a delayed time horizon for exactly this reason.)
- Sweep the filter's assumed GPS σ from 0.2 m to 15 m, giving the RMS about 20 s to settle each time. Where is the fused RMS lowest, and how does that compare with the true σp?
- Live run
- Running
- Enters ±10 % at
- –
- Overshoot
- –
- Max |θ| after 4 s
- –
x = −(u1/m) sin θ + Fgust/m, z = (u1/m) cos θ − g, θ = u2/I, with u1 = F1 + F2, u2 = (F2 − F1)L; nominal m = 1 kg, L = 0.25 m, I = 0.01 kg·m², 0 ≤ Fi ≤ m g. Reality: each rotor lags its command, τmF = Fcmd − F; the controller sees measurements one sensing/compute delay late; sensor noise 1× = σ of 2 cm, 5 cm/s, 0.5°, 2°/s; true mass and inertia = nominal × (1 + mass error) while the controller assumes 1 kg; gust = random sideways force (correlation time 1 s). Controller at 1 kHz: ax = ωo²(xtarget − x) − 2ωox, θdes = atan2(−ax, g + az) clipped to ±30°, u1 = m(g + az)/cos θ, u2 = I(ωi²(θdes − θ) − 2ζiωiθ); altitude loop az from ωz = 3 rad/s, ζ = 1. Each episode starts hovering at x = 0, z = 1.5 m with the target at x = 2 m. Good run: from 4 s to 8 s, x stays within 1.8–2.2 m, |θ| stays below 15°, and no crash (crash = touching the ground or |θ| > 90°).
Try this
- The Aggressive gains look excellent in the Ideal simulator. Press Realistic drone: what happens? Back in Ideal, add the effects one at a time at their realistic values (25 ms motor lag, 10 ms delay, 1× noise, +10 % mass, 0.3 N gust). Does any single one break the controller? Now combine just the lag and the delay.
- Press Randomise 20 worlds. How many of the 20 runs are good with the aggressive gains? Switch to Robust: the same 20 worlds are re-flown. How many are good now, and what did the robust gains give up in the ideal simulator?
- Robust gains, Realistic drone: push the motor time constant to 80 ms, outside the randomisation range. Still good? Now raise the delay from 10 ms to 30 ms as well. What happens, and what does this tell you about choosing randomisation ranges?
- Ideal simulator, gust σ = 1.5 N: which preset holds the ±10 % band better, and why? (Disturbance offset ≈ F/(m ωo²).) Robustness is a trade-off, not a free lunch.