# ComFree YAM bottle physics experiment

Independent workspace: copied YAM assets and bottle meshes, its own `.venv`,
vendored upstream checkout, scene, scripts, Warp cache, and results. Nothing imports
`rl_training`, `mjlab_gs`, or another simulator folder. No 3DGS or trained policy.

## Run

```bash
cd sims/comfree
.venv/bin/python scripts/build_scene.py
.venv/bin/python scripts/smoke.py --task rest --device cuda:1
.venv/bin/python scripts/smoke.py --task pick --gravity-compensation --device cuda:1 --video
# Same local scene/trajectory with CPU MuJoCo as a reference:
.venv/bin/python scripts/smoke.py --backend mujoco --task pick --gravity-compensation --video
# Same GPU collision stack, original MJWarp solver:
.venv/bin/python scripts/smoke.py --backend mjwarp --task pick --gravity-compensation --ccd-iterations 100 --tag reference
```

First GPU use compiles many Warp kernels and can take several minutes. Each run
uses one world, with 256 contact / 1024 constraint capacity. GPU 1 was selected
after checking available memory; pass `--device cuda:0` to change it. Video uses
MuJoCo EGL raster rendering. All dynamics for `--backend comfree` run in ComFree;
host MuJoCo forward is used only for kinematics, measurement, and rendering.

Results are written to `results/<backend>_<task>[_<tag>].json`, `.csv`, and optional
`.mp4`. Use `--tag NAME` to preserve different runs. `--backend mjwarp` selects
the vendored MJWarp solver for a like-for-like comparison.

## Scene and comparison contract

- Original YAM kinematic chain, inertias, meshes, finger collision primitives,
  gripper coupling equality, and canonical position-controller gains / effort
  limits / armatures. Gripper-only contact enablement follows the existing task.
- Bottle is 80 g, COM `[0,0,-0.006]`, diagonal inertia
  `[7.2034708e-5,7.2034708e-5,1.3698342e-5]` kg m².
- The 12 copied bottle OBJ visual parts are concatenated and seam vertices merged:
  386 vertices, 768 triangles, watertight; exact bounds in
  `assets/bottle/mesh_report.json`. No external mesh decomposition is used.
- MuJoCo/Warp mesh collision uses a **convex hull** of the supplied mesh. The
  triangle surface remains the visual shape. ComFree inherits MJWarp collision
  detection, so this evaluates a different **contact solver**, not an independent
  mesh narrowphase implementation.
- 1 ms timestep, implicitfast, pyramidal friction, dense Jacobian, contact dimension
  3. The original task's fingertip dimension 6 is deliberately reduced to 3 for
  this solver smoke; no torsional/rolling-friction equivalence is claimed.
- Scripted picking starts directly above the bottle, uses bounded arm IK, lowers,
  closes the original gripper, lifts, and holds. No attachment/weld is added to the
  bottle; its free joint always remains dynamic. This is not RL evaluation.
- `--gravity-compensation` explicitly compensates robot-body gravity while keeping
  bottle gravity active. It helps isolate contact behavior from tracking error:
  the canonical low-gain position controllers sag under gravity without it.

## Upstream and local compatibility patch

Upstream: https://github.com/asu-iris/comfree_warp

Pinned checkout: `ba8b9969f5f7859d51b1f738f458f8d2f49be080`.
Python 3.12.14, MuJoCo 3.6.0, Warp 1.12.0. Remaining exact dependencies are in
`requirements-lock.txt`. Bootstrap from scratch:

```bash
uv venv .venv --python 3.12
uv pip install --python .venv/bin/python -r requirements-lock.txt
```

The local editable install points to `./upstream`. A one-line source patch in
`patches/0001-define-unknown-sensor-frame.patch` initializes `_frame_axis`'s matrix
for an unknown object type. Without it, Warp compilation fails with
`Referencing undefined symbol: xmat`; the original traceback is retained in
`results/comfree_rest_initial_compile_failure.log`. If recloning the checkout,
apply that patch with `git -C upstream apply ../patches/0001-define-unknown-sensor-frame.patch`.

The public `comfree_warp.get_data_into` wrapper also tries to add device fields to
the `None` returned by its internal in-place copy. The experiment avoids that
wrapper and reads the device qpos/qvel arrays directly; no solver change is made.

## Results

CPU MuJoCo reference: 3 s bottle drop/rest settles at z = 0.049326 m (expected
0.049441 m). The 9 s scripted pick with gravity compensation reaches z =
0.169406 m and ends at z = 0.145961 m. Its video is
`results/mujoco_pick_gravcomp.mp4`. Without compensation, tracking sag leaves the
bottle at z = 0.099328 m and fails the diagnostic's final-height threshold.

GPU ComFree at default contact parameters passes 3 s rest: z = 0.049160 m,
0.281 mm below the nominal surface height. Its 9 s scripted pick **fails**: the
bottle remains on the floor (final z = 0.049160 m) while the gripper lifts away.
See `results/comfree_pick_gravcomp.mp4`, `.json`, and `.csv`. That run emits
`ccd_iterations=35` warnings. Increasing the narrowphase budget to 100 via
`--ccd-iterations 100` still emits warnings and fails to lift. A modest tuning
probe (`--stiffness .3 --damping .002 --ccd-iterations 100`) also fails, ending at
z = 0.049290 m. CPU reference results are **not** evidence of ComFree picking
success.

The ComFree gripper coupling deviates by up to 8.97 mm during the default pick,
versus 0.359 mm in the CPU reference. With the tuned contact parameters the
maximum deviation is 5.31 mm. This is a measured difference requiring further
investigation, not a proven explanation of the failed grasp. Collision warnings
also remain unresolved; no mesh changes were made to chase them.

The **vendored MJWarp GPU solver passes** the same scene, scripted trajectory,
gravity compensation, and CCD100 setting: final/minimum hold z = 0.145552 m,
maximum gripper coupling error = 0.360 mm. See
`results/mjwarp_pick_reference.json` and `.csv`. It also emits CCD warnings, so
the warnings alone do not explain ComFree's failed grasp. This comparison points
to solver/contact/coupling behavior as a useful next investigation; it does not
isolate a single root cause.

| Probe | Pick holds above 0.13 m | Minimum center height during hold |
|---|---|---|
| CPU MuJoCo + arm gravity compensation | Yes | 0.145961 m |
| GPU vendored MJWarp, CCD100 | Yes | 0.145552 m |
| GPU ComFree defaults, CCD35 | No | 0.049160 m |
| GPU ComFree defaults, CCD100 | No | 0.049160 m |
| GPU ComFree stiffness .3 / damping .002, CCD100 | No | 0.049290 m |

All table rows use arm gravity compensation and the same bottle/arm assets.

Success means finite state and bottle center above 0.13 m throughout the final
2 s of the 9 s pick, or resting height within 5 mm for rest. The CPU reference's
minimum center height during those 2 s is 0.145961 m; the ComFree default is
0.049160 m. These are narrow smoke criteria, not robustness, trained-policy, or
throughput benchmarks. Reported wall time excludes compilation and includes
frequent host readback, kinematics, and optional rendering.

## Primary references

- [Introduction](https://irislab.tech/comfree-doc/intro.html)
- [Installation](https://irislab.tech/comfree-doc/pages/installation.html)
- [API usage](https://irislab.tech/comfree-doc/pages/cfwarp-usage.html)
- [Contact parameters](https://irislab.tech/comfree-doc/pages/cfwarp-params.html)
- [Known issues](https://irislab.tech/comfree-doc/pages/known-issues.html)

The docs describe solver stiffness/damping sensitivity, drift, timestep dependence,
and inherited MJWarp collision limitations. Start with `--stiffness 0.2 --damping
0.001`; these are solver parameters, not mechanical spring coefficients.
