[GSoC 2026] QA of Reset of Core Systems
Organization: Open Robotics
Mentors: Arjo Chakravarty (@arjo129)
Student: Yimo (GitHub)
Project page: QA of Reset of Core Systems
Hello everyone,
This summer I worked on Gazebo’s core plugin reset path, mentored by Arjo Chakravarty. The official outcome for this project was a test suite and an audit of system reset in Gazebo, so that odd reset behaviour can be caught and does not come back later.
Gazebo ships with many core plugins. They were first written around Configure, PreUpdate, Update, and PostUpdate. Reset was added later, mainly because reinforcement learning needs a clean episode start. That path exists, but most plugins were never checked against it. If old state stays around, the next episode is not a real reset, and training becomes unreliable.
My work was in gz-sim/src/systems. I added shared reset test helpers, audited high-impact systems in batches, and opened PRs as I went: tests when reset was already clean, tests plus a fix when it was not.
About the project
The project listing is here: GSoC 2026 — QA of Reset of Core Systems.
Reset can be triggered in a few ways:
- Transport:
/world/<name>/controlwith reset parameters - GUI: the reset button sends a
WorldControlStaterequest to/world/<name>/control/state - C++ API:
Server::Reset()orServer::ResetAll() - Log playback rewind, which joins the same path
Inside the server it looks like this:
reset / rewind request → ProcessWorldControl() → UpdateCurrentInfo() → ECM rolls back to the initial state → SystemManager::Reset() (ISystemReset, or the plugin is loaded again)
A system can implement ISystemReset and clean itself up. If it does not, Gazebo destroys it and constructs it again. Either way, runtime maps and “first tick” flags are easy to get wrong. After reset, entities are restored, but they are often not reported again through EachNew(...). Plugins that only build internal state on new entities then keep using stale data, stop publishing, or crash.
I first tested through transport, because that is close to the GUI. Later I switched the tests to Server::ResetAll(), and for Contact and LinearBatteryPlugin I read ECM components directly instead of only watching topics. That was less flaky.
Shared reset test helpers
The first coding task was a single place to request reset, wait, and capture messages.
This added test/helpers/ResetUtils.hh:
- send a world reset
- split consume / apply so the reset lifecycle itself can be inspected
StepUntil/WaitUntil- a small
MsgReceiverfor topics
Existing tests (reset.cc, reset_sensors.cc, reset_detachable_joint.cc) were moved onto these helpers. Later system tests reused them instead of copying another reset loop. Backporting to gz-sim8 needed extra conflict work: PR #3596.
Audit
The project asked for coverage of core plugins, not only the ones that already looked broken. I went through src/systems and started with high-priority systems: drive, sensors, marine, battery, contact, and joints. These are the ones most likely to leak episode state into RL and other reset-heavy workflows.
Early on I expected many failures. Several motion and controller systems were already fine. That was still worth a test. Most of them had no reset coverage before.
Reset was already clean (tests added):
| Systems | Checked after reset | PR |
|---|---|---|
| DiffDrive, OdometryPublisher, AckermannSteering, MecanumDrive | pose / twist | #3546 |
| ApplyJointForce, ApplyLinkWrench, Breadcrumbs, TriggeredPublisher | force / wrench / delayed output | #3557 |
| JointController, JointPositionController, JointTrajectoryController, TrackedVehicle, TrackController | command / track response | #3637 |
| RGB-D, NavSat, AirPressure, AirSpeed, Magnetometer, ForceTorque, IMU, LogicalCamera, Altimeter | sensor topics | #3547 |
| PosePublisher | topic stability | audit only |
Old state leaked, or runtime state was not rebuilt:
| System | What went wrong | PR |
|---|---|---|
| Buoyancy | runtime data only rebuilt on EachNew() |
#3520 |
| Hydrodynamics | current / previous state not restored; scene load could crash | #3531, #3540 |
| LinearBatteryPlugin | SoC and drain state leaked | #3533 |
| Contact | sensors not rebuilt; TouchPlugin depends on this | #3541 |
| EnvironmentalSensorSystem | humidity / wind state leaked | #3556 |
| DopplerVelocityLogSystem | beam lock / range / velocity leaked | #3638 |
| DetachableJoint | detach state leaked | #3595 |
The usual fix was the same: implement ISystemReset, clear runtime maps, then rescan existing entities with Each(...) instead of waiting for EachNew(...).
Buoyancy is a clear example. After reset the entities are still there, but they are not new, so Buoyancy never rebuilt the data it only creates for new entities. Contact had the same gap. Hydrodynamics needed currentVector, prevState, and the “first iteration” flag restored. LinearBatteryPlugin had to restore SoC and avoid creating a second battery entity.
Not covered
The work above focused on high-priority systems. src/systems has more plugins than this suite covers. The rest fall into a few groups:
- Other vehicle and motion plugins
- Extra aerial and marine dynamics
- Communications
- Extra sensors and visualization
- Logging, recording, and export
- Core infrastructure (some already implement
ISystemReset, but they were not in this audit)
The same helpers in ResetUtils.hh can be reused on these groups later.
Challenges
The first surprise was that many systems already reset cleanly. That made the shared test helpers more important than a long list of bug fixes. A passing test is what stops a later change from breaking reset.
The second issue was how to test. Transport reset matches what a user does from the GUI, but it is async and easy to flake. Server::ResetAll() plus ECM reads showed plugin state more clearly. CI still failed for unrelated reasons at times; one flake is Issue #3552.
The later weeks were mostly review: comments, keeping tests and fixes in separate PRs so other CI did not go red, and backport conflicts.
Conclusion
The project goal was a test suite and an audit of core plugin reset. That is in place for the high-priority systems above. There is a shared way to write the tests, the plugins that leaked state now have a real Reset() path, and the ones that were already fine now have coverage so they stay that way.
Thanks to Arjo for the direction. Thanks also to the Gazebo maintainers who reviewed these PRs, and to Open Robotics for the chance to work on this.
Work log
I kept a running log of the audit, the daily notes, and the PR list here: