mj_recompile preserves actuator controls by actuator index instead of control-block identity
Intro
Hi!
I am testing state continuity for current variable-input actuators. mj_recompile stores actuator control values using the actuator ordinal rather than each actuator's control-block address and width.
My setup
- MuJoCo source build from
mainat10124d5d9dca411ec3c8988aa1e3b619103d71bb - C API, double precision
- Ubuntu 24.04.5 LTS, Linux 7.0.0-28-generic x86_64
- Normal GCC 11.5.0 build and GCC 13.3.0 AddressSanitizer build
What's happening? What did you expect?
I expected controls for a surviving actuator to remain associated with that actuator and all of its input slots across a recompile. Instead, SaveState and RestoreState use ctrl[i] where i is the actuator index, although current actuators have ctrladr_ and ctrlnum_ control blocks.
The attached package contains three manifestations of this one root error:
- With scalar controls
[11, 22], deleting the first actuator makes the retained actuator receive11and produce force11; its own prior command was22. - A no-op recompile of a three-input PID changes its control block from
[0, 0, 5]to[0, 0, 0], changing force from5to0. - A zero-input DC motor followed by a scalar motor has
nactuator=2andnu=1. The loop readsctrl[1]; AddressSanitizer reports ause-after-poisonread atsrc/user/user_model.cc:4103.
Representative output:
result=0 nu=1 retained-ctrl=11.0 retained-joint-force=11.0
result=0 nu=3 ctrl=0.0,0.0,0.0 force-before=5.0 force-after=0.0Relevant source:
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/src/user/user_model.cc#L4097-L4104
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/src/user/user_model.cc#L4150-L4158
- https://github.com/google-deepmind/mujoco/blob/10124d5d9dca411ec3c8988aa1e3b619103d71bb/src/user/user_model.cc#L3852-L3861
Steps for reproduction
Steps for reproduction
- Build the commit above normally and with AddressSanitizer.
- Compile the three programs in
poc/usingpoc/README.md. - Run
delete_first.ccand observe command migration. - Run
multi_input.ccand observe loss of the PID feed-forward slot on a no-op recompile. - Run
zero_input.ccagainst the ASan build and observe the out-of-bounds control read.
Reproduction package:
recompile-control-block-indexing-poc.zip
Minimal model for reproduction
Each program embeds a complete asset-free MJCF. The variants are kept separate so every output has a single causal mutation and a clear control.
Code required for reproduction
See poc/delete_first.cc, poc/multi_input.cc, and poc/zero_input.cc. I have not proposed a production patch because the cache needs a variable-width per-actuator representation and compatibility policy.
Confirmations
- I searched the latest documentation thoroughly before posting.
- I searched previous Issues and Discussions, I am certain this has not been raised before.
Source: google-deepmind/mujoco