MuJoCo 4.0.0 salió el 6 de noviembre de 2026. Las notas de la versión son largas de verdad: 61 puntos numerados en diez secciones, cuatro de ellas encabezadas con Breaking changes. El hilo del anuncio incluye una sección corta de actualización que empieza con una frase que decide si tu semana es tranquila o un desastre: “Most models load unchanged” — la mayoría de los modelos cargan sin cambios.
Esa frase es cierta para la mayoría de los modelos. No lo fue para el primero que probé, y no está escrita con la cautela que aparenta.
Esto es una auditoría por medición, no un resumen. Instalé mujoco==3.17.0 y mujoco==4.0.0 en dos entornos separados de Python 3.14 sobre la misma máquina Linux, ejecuté los mismos modelos en ambos y anoté qué cambió. Cifras medidas entre el 8 y el 11 de noviembre de 2026 en este equipo, no de un benchmark del fabricante.
El resultado: una versión que de verdad mejora en lo que anuncia y que rompe cosas en un punto donde no lo anuncia.
Lo que 4.0.0 dice de sí misma
| Dato del anuncio | Valor |
|---|---|
| Descargas en PyPI | ~3,6 millones al mes |
| Estrellas en GitHub | “más de 16K” |
| Versiones desde 3.0 | 39 |
| Contribuidores externos desde 3.0 | más de 45, ~2.000 commits en el último año |
| Contactos especulativos | un brazo atrapa una tarjeta de 0,7 mm a 2 m/s con paso de 16 ms; la detección discreta falla por encima de 8 ms |
| Ganancia por pasos más grandes | “de 6 a 9 veces más rápido” en escalada de humanoides y agarre de objetos finos |
| Varillas elásticas 1D | deflexión de viga en voladizo Euler-Bernoulli “dentro del 0,5%” en CPU y Warp |
| Reconstrucción de colisiones (trabajo de 3.x) | ~4× más rápido que multicontact con libccd, 2× en convexos de malla grande |
Las cuatro últimas son las que no reproduje: necesitan sus propios bancos de prueba y su propio hardware. Lo que sí reproduje es la parte aburrida y estructural: precisión, comportamiento al cargar, diseño del paquete y los dos cambios de dinámica que alteran resultados que creías estables.
13 de 61 cambios están en una caja de advertencia. Otros cinco cambian tu dinámica desde fuera
Las notas meten 13 puntos numerados dentro de cuatro bloques Breaking changes (Engine, Deformables, Compiler, Samples). Los otros 48 son funciones y correcciones normales. La proporción tranquiliza hasta que miras cuáles cambios de comportamiento quedan fuera de las cajas.
| Punto | Dónde está en las notas | Qué cambia en realidad |
|---|---|---|
#4 envoltura sidesite | Engine, en la caja | Regla del tendón: test de semiplano → regla de homotopía |
| #5 orientaciones RK4 | Engine, fuera de toda caja | Orden de integración 2 → 4 en articulaciones ball/free |
| #24 amortiguación de aristas en flex | Deformables, fuera | Se integra explícita bajo implicit/implicitfast |
| #46 acelerómetro en cuerpos static/mocap | Bug fixes | Devuelve 0 → devuelve aceleración gravitatoria propia |
| #47 actuadores saturados | Bug fixes | Dejan de aportar derivadas implícitas |
#50 solref de pares | Bug fixes | Mínimo elemento a elemento → mezcla solmix |
El punto 5 es la víctima más clara del formato: dice, con todas las letras, “This changes the simulation of models with ball or free joints that use RK4”, y está en una sección cuya caja de advertencia se cerró dos puntos antes. Si solo lees las cajas, te lo pierdes.
El punto 47 es peor que un problema de maquetación. Está en medio de 16 correcciones de errores, sin ninguna marca, y elimina el amortiguamiento numérico justo de los actuadores que estaban siendo recortados. Desde el punto de vista del usuario eso no es una corrección: es un cambio de régimen de estabilidad archivado como reparación.
Medición 1: RK4 sí es de cuarto orden ahora — 2.959 veces mejor en tu paso por defecto
La afirmación es precisa y comprobable: las etapas de Runge-Kutta se combinaban sin la corrección Runge-Kutta–Munthe-Kaas, así que las orientaciones convergían en segundo orden aunque el método fuera RK4. Arreglarlo debe verse como un cambio de pendiente en la gráfica error-paso, y se ve.
Montaje: una articulación libre, un cuerpo rígido con inercia asimétrica, sin gravedad, 0,5 s de tiempo simulado. Error = norma de la diferencia entre componentes del cuaternión contra una integración de referencia con dt = 1,5625e-5. Usé diferencias de componentes y no un ángulo con arccos, porque la métrica arccos(|dot|) se satura cerca de 1e-8 y aplana precisamente los errores que quieres medir.
dt | Error 3.17.0 (rad) | Error 4.0.0 (rad) | Mejora |
|---|---|---|---|
| 0,01 | 3,778e-04 | 1,277e-07 | 2.959× |
| 0,005 | 9,445e-05 | 8,076e-09 | 11.695× |
| 0,0025 | 2,361e-05 | 5,077e-10 | 46.504× |
| 0,00125 | 5,903e-06 | 3,183e-11 | 185.451× |
| 0,000625 | 1,476e-06 | 1,997e-12 | 738.793× |
Orden de convergencia ajustado: 2,0000 en 3.17.0 y 3,9916 en 4.0.0. Un segundo caso, giro casi axial (ω = 0,4; 9; 0,3), da orden 1,9999 → 3,9847 y una reducción de error de 1.052× con dt = 0,01. La relación de cuatro veces por cada mitad de paso es la firma que predice el análisis, así que no es una constante afortunada.
Consecuencia práctica: en los pasos que la gente usa de verdad (5–16 ms), RK4 de 4.0 es ahora mejor que RK4 de 3.17 con 1/32 del paso. La otra cara, que las notas no dicen: tus trayectorias viejas estaban mal, no las nuevas. Si tienes tests de regresión anclados a la salida de RK4 en 3.x, van a fallar, y el cambio juega a tu favor.
Medición 2: el propio brazo de dos eslabones del proyecto no carga hasta que lo migras
El punto 4 cambia la semántica de sidesite. Las notas son honestas con el mecanismo: el viejo test de semiplano podía hacer que el tendón saltara sobre el geom y podía elegir el lado equivocado en envolturas de más de 180°. La regla nueva es “el camino más corto homotópico a esta polilínea en el plano de envoltura con el geom eliminado”, y admiten que conserva una discontinuidad cuando un extremo cruza el rayo opuesto al side site.
También prometen que la migración es un no-op para el ejemplo incluido: los side sites de model/tendon_arm/arm26.xml “se movieron de justo debajo de las articulaciones a 45 grados por debajo del brazo, lo que reproduce exactamente las longitudes de tendón anteriores”.
Las dos mitades de la frase son ciertas y las verifiqué por separado.
La promesa se cumple. Tomé el arm26.xml anterior a 4.0 en 3.17.0 y el migrado en 4.0.0, barrí ambas articulaciones en 41 puntos y comparé las 246 longitudes de tendón. Diferencia absoluta máxima: 2,2e-16. Idéntico a nivel de coma flotante; la aritmética de longitud de cuerda no se ha movido.
La migración no es opcional, y el fallo está mal diagnosticado. Cargar el archivo viejo bajo 4.0.0 no da una advertencia sobre envoltura de tendones. Da esto:
ValueError: Error: Lengthrange computation did not converge in actuator 1:
eval (0.218496, 0.535486) range (0.340326, 0.535486)
Nada en ese mensaje menciona sidesite, ni envoltura, ni homotopía, ni las notas de la versión. El tendón da la vuelta al geom por el otro lado, la búsqueda del rango de longitud del actuador encierra un valor que no puede alcanzar y el compilador informa un fallo de convergencia en un actuador. Un equipo que se topa con esto empezaría —con toda lógica— a depurar sus definiciones de actuador, sus atributos lengthrange o sus rangos articulares. La palabra que ahorraría una tarde no está en el error.
Las coordenadas nuevas son (0,106; -0,106; 0) donde las viejas eran (0; -0,15; 0) — 0,15 a 45°, exactamente lo descrito. Ese es el arreglo completo: dos números por side site.
Buscar sidesite en los issues de GitHub devuelve 25 resultados y 4.0.0 devuelve 16; ninguno reporta este fallo de lengthrange. Cuatro días después de la publicación, es un modo de fallo no reportado, no uno conocido.
Medición 3: una “corrección de error” que convierte una articulación congelada en 447 rad/s
El punto 47 dice que los actuadores saturados “ya no aportan derivadas implícitas”, y añade en el mismo párrafo: “los modelos con eslabones ligeros y sin armature pueden necesitar armature articular o un paso menor”.
Eso suena a nota de rendimiento. Es una frontera de estabilidad. Aquí hay una bisagra, un actuador de posición (kp = 200), un actuatorfrcrange lo bastante bajo para que recorte, y 1,0 s de simulación. qpos final y velocidad articular máxima:
| Configuración | 3.17.0 | 4.0.0 |
|---|---|---|
kv=2000, dt=0,02, sin armature | q = 0,063746, máx abs qvel = 0,125 | q = 26,117906, máx abs qvel = 447,56 |
kv=2000, dt=0,01, sin armature | q = 0,126236 | q = 29,889235 |
kv=2000, dt=0,02, armature=0,01 | q = 0,063730 | q = 4,196720, máx abs qvel = 9,78 |
En 3.17 la articulación apenas se mueve: la derivada implícita del actuador saturado actuaba como amortiguamiento numérico, así que el modelo se quedaba en q ≈ 0,064 y parecía un servo rígido y bien comportado. En 4.0 ese amortiguamiento desaparece —correctamente, porque nunca fue físico— y el mismo modelo se desboca a 447 rad/s. Añadir armature=0,01 devuelve un suelo de inercia físico y el modelo vuelve a comportarse.
Dos cosas convierten esto en lo más peligroso de la versión:
- No avisa. Sin mensaje, sin flag, sin capa de compatibilidad. El modelo compila, corre y produce números.
- En la versión vieja, el fallo parecía éxito. Un modelo que devuelve un q sospechosamente constante no es un modelo que funciona: es uno que estaba sostenido por un artefacto numérico que 4.0 corrige. Cualquier suite que afirmara “la articulación se queda cerca de cero” ahora está blindando el artefacto.
Las trayectorias divergen en cuanto kv ≥ 1 bajo implicit, implicitfast o discrete. Si tus robots usan servos de posición con límites de fuerza y sin armature —la mayoría de los brazos con control de posición—, este es el punto que hay que probar antes de actualizar, no después.
Lo que 4.0 elimina, y lo que no ofrece en su lugar
- El plugin
cable(plugin/elasticity) y el elemento<composite>desaparecen, sustituidos porflexcomp1D con<elasticity>. Cargué un modelo de cable de terceros: 3.17.0 lo acepta (nq=157, 42 cuerpos), 4.0.0 lo rechaza conXML Error: Schema violation: unrecognized element. - Los flexcomp elásticos abiertos de 1D con al menos dos elementos ahora crean cuerpos de torsión y articulaciones por arista. En una rejilla de varilla de 11 segmentos:
nq33 → 44,nbody12 → 23,njnt33 → 44, con 11 articulaciones_twist_Nynplastic = 76. Las notas sí advierten que esto “desplaza los índices de cuerpos y articulaciones declarados después del flexcomp”: cualquier código que indexeqpospor número queda mal, incluidos los keyframes. - Las restricciones de igualdad
flexvertse eliminan del todo;mjEQ_FLEXVERTsobrevive solo como marcador que da error si se usa. mujoco.experimental.studiopasa amujoco.studio; se abandona Python 3.10.
Las notas dan receta de migración para todo esto (13 puntos llevan párrafo Migration: explícito), mejor documentación de la que consiguen casi todos los proyectos. Lo que no ofrecen es ningún flag de compatibilidad. No hay interruptor para recuperar el test de semiplano en sidesite, ni opt-out de la corrección RK4, ni forma de reactivar el amortiguamiento implícito en actuadores saturados, ni modo que cargue un <composite>. Actualizar es una puerta de un solo sentido, y la única herramienta para cruzarla es tu propia suite de pruebas.
Con las constantes elásticas de flex (punto 11) la corrección es real: el estiramiento 3D usaba el doble del primer parámetro de Lamé, el 2D un cuarto del módulo de cizalla, y la flexión 2D ¾(1-ν)D en vez de la rigidez de placa. Los modelos quedan más rígidos. Verifiqué las fórmulas de migración contra los modelos ya remapeados del repo y coinciden exactamente — poisson 0,2 → 0,285714286 (2ν/(1+2ν)), young 5e4 → 53571,4286 (E(1+ν')/(1+ν)), 2D 6e6 → 3,6e6 (0,75(1-ν)E). Si calibraste parámetros de cuerpo blando, los calibraste contra ecuaciones equivocadas, y el remapeo conserva tu calibración en lugar de tus parámetros.
El paquete partido: 72 MB → 62 MB → 29 MB, y un pip que rompe
mujoco son ahora tres distribuciones: mujoco-core (bindings y librería C, solo numpy), mujoco-studio (Studio y Filament) y mujoco (ambas). Medido aquí:
| Instalación | Versión | Tamaño | Archivos | Import (mediana de 5) |
|---|---|---|---|---|
mujoco | 3.17.0 | 72 MB | 304 | 446 ms |
mujoco (por defecto) | 4.0.0 | 62 MB | 264 | 450 ms |
mujoco-core | 4.0.0 | 29 MB | 199 | 346 ms |
El corte ahorra un 59% de instalación para quien no renderiza y unos 100 ms de import (23%). El paquete por defecto no ahorra nada medible —450 ms frente a 446 ms— y así hay que leerlo: la ganancia es para quien nunca quiso el renderizador.
La ruta de actualización, en cambio, es una trampa. pip install -U mujoco encima de una instalación 3.17.0 deja site-packages/mujoco en 121 archivos y 38,8 MB, con import mujoco roto. Las notas sí explican la causa —pip borra archivos que se movieron a mujoco-core— y dan el comando de reparación, que funcionó tal cual:
pip install --force-reinstall --no-deps mujoco mujoco-core mujoco-studio
Después: 264 archivos, 63,2 MB, 4.0.0 importa. Aun así, el bloque “Getting Started” del anuncio abre con pip uninstall -y mujoco “si actualizas desde 3.x” —un condicional en un comentario, encima del comando que la gente va a pegar. La secuencia limpia es:
pip uninstall -y mujoco && pip install mujoco
mujoco-core no incluye mujoco.viewer, mujoco.mjrf ni Studio. Sí incluye algo nuevo: mujoco/doc/, las fuentes Sphinx más 7 archivos markdown de skills para agentes, para que un agente LLM con el paquete instalado pueda leer el manual sin conexión en vez de inventarse firmas de mj_step.
Salud del repositorio
| Métrica | Valor (11/11/2026) |
|---|---|
| Estrellas / watchers / forks | 16.908 / 141 / 1.991 |
| Ratio de watchers | 0,834% |
| Ratio de forks | 11,78% |
| Versiones muestreadas | 58, 466 assets |
| Intervalo mediano entre versiones | 29 días (máx. 90) |
| Contribuidores / commits | 100 / 6.283 |
| Cuota del mayor contribuidor | 25,8% (1.619 commits) |
Commits vía copybara-github | 337 (5,4%) |
Un 0,83% de watchers es bajo para un proyecto de esta importancia: la gente le da estrella a MuJoCo como se la daría a un estándar, no a un repo que piensa seguir. La entrada copybara-github conviene conocerla: el 5,4% de los commits llega por la sincronización interna de Google, y por eso algunos cambios aparecen como una exportación grande en vez de una discusión.
“Tiene 305 issues abiertos” también es falso tal como se cita: el open_issues_count de la API suma issues y pull requests. En el momento de leer era 153 + 152; la página en vivo el 11 de noviembre mostraba 157 issues y 176 pull requests.
Lista de actualización, en orden
Todo lo siguiente es una comprobación que ejecuté; el orden importa porque cada paso falla de forma distinta.
- Reinstala limpio y verifica el número de archivos.
pip uninstall -y mujoco && pip install mujoco, y confirma que el directorio del paquete tiene ~264 archivos, no ~121. - Carga todos los modelos antes de simular ninguno. Si alguno lanza
Lengthrange computation did not converge, mira primero sus elementossidesite, no sus actuadores. Comprueba que los extremos no cruzan el rayo opuesto al side site en todo el rango articular. - Busca lo eliminado.
grep -rl '<composite' models/ grep -rl 'plugin="mujoco.elasticity' models/ grep -rl '<flexvert\|equality="vert"' models/ - Audita los actuadores saturados. Localiza actuadores de posición/velocidad con
forcerangeoactuatorfrcrangeque recorten de verdad. Si la articulación no tienearmature, añade uno (empieza por0,01) o baja el paso, y compara antes/después en ambas versiones. - Compara trayectorias, no escalares. Ejecuta el mismo
q0,v0,ctrlen 3.17 y 4.0 con un horizonte suficiente para que la divergencia aparezca. Espera deriva de orientación con RK4 y deriva de rigidez en flex no interpolados; un cambio de estabilidad significa que encontraste el punto 47. - Rehaz el código basado en índices. Todo lo que lea
qpos/qvel/índices de cuerpo por número, keyframes incluidos, es sospechoso en varillas 1D tras el cambio de articulaciones de torsión. Dirígete a cuerpos y articulaciones por nombre. - Fija por paquete, no por costumbre. Si no puedes migrar ahora, fija
mujoco==3.17.0y no referencesmujoco-coreen un requirements de 3.x: esos nombres no existen antes de 4.0.
El documento que las notas no enlazan
MuJoCo incluye VERSIONING.md, y debería ser lo primero que lea cualquiera antes de actualizar. Explica por qué los números de versión parecen raros —desde 3.5.0 el esquema es SUPERMAJOR / MAJOR / MINOR_OR_PATCH, donde MAJOR significa cambios incompatibles— y luego dice en voz alta lo que casi nadie dice:
“las propiedades numéricas de la integración del estado físico implican que, pese a ser determinista, la reproducibilidad numérica entre versiones casi con seguridad no se mantendrá”
y más adelante, sin rodeos: “la reproducibilidad numérica nunca está garantizada”.
Ese es el marco honesto para todo lo anterior. MuJoCo no promete que tus números sobrevivan a una actualización; te dice que no lo harán. Lo que hay que calificar es entonces la clase de cambio: que un término de segundo orden pase a cuarto es una mejora de calidad, que una constante de flex esté desviada 4× es un arreglo, y que un actuador saturado pierda su amortiguamiento implícito es un cambio de estabilidad que puede convertir un controlador que funciona en una oscilación de 447 rad/s. Solo uno de los tres te detiene en seco, y es el archivado bajo “Bug fixes”.
Las notas de 4.0.0 no enlazan VERSIONING.md en ningún sitio. Léelo igual.
Preguntas frecuentes
¿Es 4.0 más rápido en simulación rígida normal? La cifra de 6 a 9× del anuncio viene de pasos más grandes en tareas concretas (escalada de humanoides, agarre de objetos finos) usando contactos especulativos, no de un solver más rápido. Para un modelo que ya corre con un paso cómodo, las ganancias son las de la pila de colisiones de la línea 3.x, no las de 4.0.
¿Puedo conservar el comportamiento 3.x sin reescribir modelos?
Para las constantes elásticas de flex, sí: las fórmulas de migración reproducen el comportamiento viejo con parámetros nuevos, y los modelos del propio repo lo demuestran. Para RK4, sidesite, actuadores saturados y <composite>, no: no hay flag de compatibilidad ni cargador de doble modo.
¿Por qué mi modelo falla al cargar con un error de convergencia de lengthrange?
Mira sidesite primero. El tendón da la vuelta al geom por el otro lado, así que el rango de longitud del actuador no se puede encerrar. Mover cada side site al centro del arco envuelto —el arm26.xml incluido usa exactamente 45°— lo arregla y conserva las longitudes con 2,2e-16 de error.
¿Necesito mujoco-core o el paquete completo?
mujoco-core si nunca renderizas: 29 MB en vez de 62 MB y ~100 ms menos de import. Le faltan mujoco.viewer, mujoco.mjrf y Studio. mujoco sigue siendo el valor por defecto seguro.
¿El cambio de sensores del punto 46 rompe mi estimador de estado?
Puede. Los sensores de aceleración y framelinacc en cuerpos estáticos y mocap devolvían cero y ahora devuelven la aceleración gravitatoria propia (un fallo introducido en 3.3.7). Si tu estimador asumía cero para una IMU estática en el marco del mundo, ahora ve 9,81 m/s² apuntando hacia arriba.
¿Actualizo ya?
Si usas RK4 con articulaciones ball o free, sí: la ganancia de precisión es grande y gratis. Si tienes brazos con control de posición, límites de fuerza y sin armature, actualiza después de ejecutar los pasos 4 y 5. Si dependes de <composite> o del plugin cable, lo que tienes delante es una reescritura, no una actualización.
Conclusión
MuJoCo 4.0.0 es el mejor tipo de versión mayor: corrige matemáticas reales (orientaciones RK4, constantes de flex, homotopía de tendones) y documenta las migraciones con honestidad, punto por punto, con fórmulas. Su punto débil es estructural, no técnico: las notas ponen 13 puntos en cajas de advertencia y dejan cinco cambios de dinámica fuera, uno de ellos una frontera de estabilidad disfrazada de corrección.
La medición que más importa no es el 2.959×. Es que q = 0,063746 pase a q = 26,117906: el mismo modelo, el mismo paso, el mismo controlador, un orden de magnitud de diferencia en comportamiento, y nada en la salida que te avise de que ocurrió algo. Actualiza con método y lleva tus propias trayectorias para comparar: la versión no te va a decir cuáles de las tuyas descansaban sobre un artefacto.