google-deepmind / google-deepmind/mujoco
mj_recompile resets documented integration-state fields on a no-op recompile
- Dominant language
- C++
- Stars
- 15.2k
- Forks
- 1.8k
- Avg merge
- 10d 16h
- Merged PRs (30d)
- 25
Description
### Intro
Hi!
I am testing state continuity across live `mjSpec` recompilation. I found that a no-op `mj_recompile` preserves a subset of `mjData` but resets other fields that MuJoCo documents as integration state.
### My setup
- MuJoCo source build from `main` at `10124d5d9dca411ec3c8988aa1e3b619103d71bb`
- C API, double precision
- Ubuntu 24.04.5 LTS, Linux 7.0.0-28-generic x86_64
- CMake 3.28.3 and GCC 11.5.0
### What's happening? What did you expect?
The API reference says `mj_recompile` preserves the integration state while accounting for added or removed degrees of freedom. On an unchanged specification, I therefore expected persistent integration-state and user-input fields to remain unchanged.
The implementation saves `time`, `qpos`, `qvel`, `act`, `ctrl`, and mocap poses, resets a newly allocated `mjData`, and restores only that subset. In the reproduction, `qfrc_applied`, `xfrc_applied`, `eq_active`, `userdata`, and `qacc_warmstart` all become zero. The omitted applied force changes the next-step velocity from `0.37` to `0.30`.
```text
before time=3.25 qpos0=0.40 qvel0=0.30 qfrc0=7.00 xfrc0=8.00 eq_active0=1 userdata0=123.00 warmstart0=456.00
after time=3.25 qpos0=0.40 qvel0=0.30 qfrc0=0.00 xfrc0=0.00 eq_active0=0 userdata0=0.00 warmstart0=0.00
next-step velocity: lost-force=0.300000 restored-force=0.370000 delta=0.070000
```
The unchanged `time`, `qpos`, and `qvel` are positive controls; the velocity difference is a physical consequence rather than only an internal-state mismatch.
Relevant implementation and documentation:
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/doc/APIreference/functions.rst#L97-L112
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/doc/programming/simulation.rst#L264-L395
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/src/user/user_api.cc#L324-L357
### Steps for reproduction
1. Check out the commit above and build MuJoCo.
2. Compile and run `poc/reproducer.cc` using `poc/README.md`.
3. Observe that the no-op recompile resets the seeded fields and changes the next step.
Reproduction package:
[recompile-incomplete-integration-state-poc.zip](https://github.com/user-attachments/files/32221251/recompile-incomplete-integration-state-poc.zip)
### Minimal model for reproduction
The complete model is embedded in `poc/reproducer.cc`. It contains two slider bodies and one equality constraint; no external assets are required.
### Code required for reproduction
The attached single-file C++ program is the minimal executable reproduction. I have not attached a production patch because preserving all documented state across schema changes needs an explicit per-object mapping policy rather than a larger anonymous copy.
### Confirmations
- [x] I searched the [latest documentation](https://mujoco.readthedocs.io/en/latest/overview.html) thoroughly before posting.
- [x] I searched previous [Issues](https://github.com/google-deepmind/mujoco/issues) and [Discussions](https://github.com/google-deepmind/mujoco/discussions), I am certain this has not been raised before.
Contributor guide
Research direction
Start with src/user/user_api.cc at the mj_recompile implementation, then compare its state handling with the API and simulation documentation linked in the issue. Run poc/reproducer.cc using poc/README.md; the work is done when the documented integration-state and user-input fields remain correct after a no-op recompile and the next-step velocity is unchanged.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- cpp
- Domain
- robotics
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 42/100