$npx -y skills add ForgeCAD/forgecad-public-kit --skill forgecad-verify-mujocoVerify a ForgeCAD MJCF export in MuJoCo with dynamics, contacts, controls, joint travel, and rendered evidence before calling it simulation-ready.
| 1 | # Verify MuJoCo |
| 2 | |
| 3 | Use this when `forgecad export mjcf ...` is part of the deliverable. A model is not sim-ready just because `forgecad check simready` passes or the MJCF file loads: it must be loaded in MuJoCo, stepped under gravity, driven with the intended controls, contact pairs inspected, and rendered from useful views. |
| 4 | |
| 5 | Routing: geometry-only visual inspection -> `forgecad-inspect-model`; model authoring/API questions -> `forgecad`; building a new model -> `forgecad-build-model`. |
| 6 | |
| 7 | ## Definition Of Done |
| 8 | |
| 9 | 1. **Export from the exact source file.** |
| 10 | ```bash |
| 11 | rm -rf /tmp/forgecad-mjcf && mkdir -p /tmp/forgecad-mjcf |
| 12 | forgecad export mjcf path/to/model.forge.js --output /tmp/forgecad-mjcf |
| 13 | ``` |
| 14 | 2. **Load the generated scene in MuJoCo.** Use `scene.xml`, not only the model XML, so the floor/camera/package context is included. |
| 15 | 3. **Check the root behavior.** If the model has a free root, it needs real support/contact geometry or an explicit fixed-root export path. Do not hide a floor failure by turning the whole shell into a giant bounding box that blocks moving internals. |
| 16 | 4. **Check initial poses numerically.** For mechanisms, compute the expected body/link axes from the design source and compare them to MuJoCo `xmat`/`xpos`. Joint `ref`/default and connector frames can disagree with visual intuition. |
| 17 | 5. **Keep meaningful collisions.** Do not mark moving functional parts `Sim.collider.none(...)` just to make motion pass. If full visual mesh contact is unstable, use a physically defensible simplified collider or proxy and state what physical surface it represents. |
| 18 | 6. **Run the intended control with numeric acceptance criteria.** Define the expected signed post-settle joint travel before running the test: e.g. "this drive should rotate the drum -0.04 to -0.06 cycles, then stop near zero velocity" or "this wheel should move at least +1 cycle and keep spinning". Use cycles for revolute/indexing mechanisms when that is easier to reason about, and radians for direct MuJoCo qpos checks. A controller that only jitters, moves the wrong direction, overshoots through a stop, or eventually jams after skipping the intended state is a failure even when the process exits 0. |
| 19 | 7. **Inspect contact pairs.** Contact names should match the physical story: floor/support, card/card, wheel/ground, stop/follower, etc. Contacts against a filled-in AABB, hidden fixture, or unrelated side plate usually indicate bad collider selection. |
| 20 | 8. **Render evidence.** Save initial, settled, and driven frames from views that actually show the moving parts. If the model orientation is not obvious, generate a labeled camera preview grid first, inspect it, then rerun with the azimuth/elevation that shows the functional face. Do not report GIFs/frames before visually confirming they are not from the back, underside, or an occluded side. |
| 21 | |
| 22 | ## Helper Script |
| 23 | |
| 24 | This skill ships a MuJoCo smoke verifier: |
| 25 | |
| 26 | ```bash |
| 27 | uv run --python 3.11 --with mujoco --with pillow \ |
| 28 | python <this-skill-dir>/scripts/mujoco_verify.py /tmp/forgecad-mjcf \ |
| 29 | --settle-seconds 2 \ |
| 30 | --seconds 8 \ |
| 31 | --actuator drum_velocity=-0.75 \ |
| 32 | --watch-joint drum_joint \ |
| 33 | --expect-drive-cycles drum_joint=-0.06:-0.03 \ |
| 34 | --expect-final-qvel drum_joint=-0.02:0.02 \ |
| 35 | --render-dir /tmp/forgecad-mjcf/verify \ |
| 36 | --camera-preview-grid |
| 37 | ``` |
| 38 | |
| 39 | Use `--actuator name=value` more than once for multi-actuator models. Use `--expect-drive-cycles joint=min:max` to assert signed final-minus-settled revolute travel in cycles/turns. Use `--expect-drive-delta joint=min:max` when you want raw MuJoCo qpos units instead. Use `--expect-final-qvel joint=min:max` to assert terminal velocity when the mechanism should stop or continue at a bounded speed. The script prints JSON with root drift, initial-to-final joint delta, post-settle drive delta, derived cycle counts, final velocities, expectation ranges, and top contact pairs, then writes PNG frames if `--render-dir` is supplied. |
| 40 | |
| 41 | Prefer explicit travel envelopes over loose "it moved" checks: |
| 42 | |
| 43 | - Stops/latches: assert a signed drive cycle or delta range and a near-zero final velocity range. |
| 44 | - Continuous drives: assert the signed drive cycle/delta is large enough over the run and final velocity remains in the expected direction/range. |
| 45 | - Indexing mechanisms: assert the expected cycle step size, not just eventual stall. If the mechanism reaches a stop only after skipping several indices, treat that as failure. |
| 46 | - Gravity-settling mechanisms: evaluate functional travel with post-settle drive delta, not initial-to-final delta. |
| 47 | |
| 48 | When rendered orientation matters, start with `--camera-preview-grid`, open `camera_preview_grid.png`, pick the azimuth that shows the mechanism face or contact interface, then rerun with `--camera-azimuth <deg>` and any needed `--cam |