#3596·mujoco

mj_recompile preserves actuator controls by actuator index instead of control-block identity

Author: peachtree0222Created Sep 15, 2026Updated Sep 15, 2026
Labelsbug

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 main at 10124d5d9dca411ec3c8988aa1e3b619103d71bb
  • 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:

  1. With scalar controls [11, 22], deleting the first actuator makes the retained actuator receive 11 and produce force 11; its own prior command was 22.
  2. A no-op recompile of a three-input PID changes its control block from [0, 0, 5] to [0, 0, 0], changing force from 5 to 0.
  3. A zero-input DC motor followed by a scalar motor has nactuator=2 and nu=1. The loop reads ctrl[1]; AddressSanitizer reports a use-after-poison read at src/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.0

Relevant source:

Steps for reproduction

Steps for reproduction

  1. Build the commit above normally and with AddressSanitizer.
  2. Compile the three programs in poc/ using poc/README.md.
  3. Run delete_first.cc and observe command migration.
  4. Run multi_input.cc and observe loss of the PID feed-forward slot on a no-op recompile.
  5. Run zero_input.cc against 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