Case study · simulation, not hardware
A compliant snake robot, simulated before it is built.
A 7-segment, cable-driven snake robot that I will build with micro servos and 3D-printed parts. Before buying anything, I built a MuJoCo simulation to test one idea from the literature: a partly compliant body gets through cluttered terrain better than a stiff or a loose one (Wang et al., Science Robotics 2023).
Move across the figure to set the body’s compliance G, from stiff (0) to loose (1.5). The read-out gives the real results at that G from the two runs below. The drawing is an illustration; the numbers are from the logs.
The question
- Does intermediate cable compliance beat stiff and loose bodies in a post lattice, under the same open-loop wave?
- What does the real robot need: joint torque, cable tension, servo class, segment length?
- Is adaptive slack worth building: loosen when jammed, tighten to push?
The robot
| Body | 7 rigid segments, 6 yaw joints, 0.10 m pitch, planar |
| Actuation | One MG90S micro servo per joint driving a bilateral cable spool (0.2027 N·m stall at 6 V) |
| Ground | A passive wheel under each segment: 1.6 : 1 sideways-to-forward drag, the paper’s ratio |
| Mass | 0.038 kg per segment, 0.075 kg head (ESP32 and battery). Placeholders until weighed |
| Compliance | The paper’s generalized compliance G, from 0 (stiff) to 1.5 (loose) |

MuJoCo render, run 20261017T112843Z_29c5654.
The arena
A hexagonal lattice of 0.103 m posts at the paper’s densities. The robot must cross 1.95 m within 40 gait cycles. Every condition runs from the same randomized starts, so comparisons are paired.

How I worked
- Decision gates
Eight points where I chose between written options with trade-offs. Every choice and its reason is logged.
- Pre-registration
Pass/fail rules were written down before each run, then applied by code. When the rule says “inconclusive”, the project stops there.
- Tests that can fail
133 tests, each shown to fail on a planted fault: a flipped sign, a dropped row, a mistyped config value.
- Disclosure
The code was written with Claude Code under my direction. My decisions are logged in
docs/decisions.md.
Pipeline
Tagged YAML configs are the one source of truth. Python generates the MuJoCo model, and a parallel, resumable sweep runner writes each run’s config, git commit, JSONL log and Parquet copy. Dependencies are locked with uv, and logged trials replay bit-for-bit in a fresh environment.
Every trial
One dot per simulated trial. A filled dot crossed the lattice. Hover a column.
What I found
160 trials, no success at any compliance. A diagnostic re-run showed every overload came from a post holding a joint near the head. All 84 overloaded trials reproduced exactly.
run20261017T062232Z_b5aac2aSuccesses out of 5 for G = 0 to 1.5: 5, 4, 1, 2, 5, 0, 0. The pre-registered outcome was inconclusive, so no setup was adopted.
run20261017T111457Z_cc9bd78On open floor all 40 trials succeeded and mid-G speed matched the ends. In the lattice mid-G again did worst: 8/10 and 6/10, against 10/10 at both ends. The stall needs the posts.
run20261018T071302Z_d73fb14

Same start, seed 1002: stuck at G = 0.75, passing at G = 1.0.
Hardware decision
The MG90S became the baseline servo and the MG996R was dropped: it still overloaded at G = 0, is 4× heavier, barely fits the segment, and may turn only 120°.
What it taught me
- An inconclusive result reported honestly is worth more than a tuned success.
- Writing a criterion down before the data exists exposes bad criteria. One of mine was true by construction at G = 0.
- Most of the work was ruling out artefacts: start-pose bugs, friction priority, metric artefacts, solver resets.
Next
A cable-level model, checked against the current joint-level one, to test whether the mid-compliance stall comes from the simplification. Then the full sweep, servo sizing, adaptive slack and sensitivity.