# MAO - Flexible-Spine Sim-to-Real RL Quadruped
> 📌 Original write-up: [LinkedIn post](https://www.linkedin.com/posts/ksenia-tiuliubaeva-607a99299_robotics-reinforcementlearning-simtoreal-ugcPost-7490891067920289792-CGI6)
MAO - a 12-DOF quadruped with a passive flexible spine, built as a sim-to-real RL testbed: MuJoCo → real hardware.
## HARDWARE
- 12× LX-16A bus servos (hip abduction / hip pitch / knee). Passive TPU spine.
- Measured step response at 301 Hz: ~370°/s slew, ~38 ms latency, first-order τ ~53 ms.
- Sensing: 3× IMU (2× GY-87, 1× GY-521) at 200 Hz, 4× FSR-402 foot sensors, 4× knee contact buttons, STM32 hub over USB-CDC @460800.
## CONTROL
50 Hz loop (20 ms) - exactly the training rate (MuJoCo 500 Hz × frame-skip 10). Bus cycle ~13 ms: 12 writes ×0.86 ms + one round-robin read ×3 ms (~75 Hz ceiling), so full servo state refreshes every ~240 ms. Policy commands stay ≤200°/s per joint, inside the measured 370°/s limit.
## OBSERVATION DESIGN - 150-dim, sim-to-real honest
Every channel is computable on the physical robot: no privileged simulator state, no mocap. Torso height comes from FK relative to the support feet; global quantities are replaced by onboard proxies.
- Soft spine made observable via 3 IMUs and curated pairwise features: bend, twist, relative turn, S-shaped lateral-curvature proxy.
- Motion history without a frame stack or RNN: no-load timers, movement toward target zones, lift-vs-drag clearance, support age, settle counters, spine-violation timers.
- Per-joint tracking error (qpos - ctrl) and persistent high-error timers expose command-tracking mismatch.
- Contact: analog FSR, knee contact, foot-in-zone geometry in yaw-invariant torso frames.
- Goal-conditioned: a 7-dim goal descriptor gives sit, stand and play-bow from one policy.
Known blind spot: relative torso translation stays unobserved, limiting precise manoeuvring.
## ENGINEERING WORTH MENTIONING
- Observation-bridge verification: deployment code recomputes all 150 dims independently and matches the training env to printed precision over full simulated trajectories.
- record → replay → compare: a policy's exact command sequence replayed open-loop on hardware, joint/IMU/FSR traces compared. Measured delay, τ and slew rate ground the actuator model.
- Compass yaw fusion: 2× hard-iron-calibrated, tilt-compensated QMC5883P; rear yaw unwrapped relative to front before midpoint and IMU fusion.
- Brownout-aware deployment: bus-voltage preflight, runtime undervoltage abort, ~7° mechanical-stop margin, controlled lowering before torque-off.
- Motion authoring outside MuJoCo: pure-Z shoulder squat via per-leg damped-least-squares IK - planted feet, 0.00 mm X/Y drift.
## RESULT
Cloud→sit, cloud→stand and legacy hand-authored trajectories replayed on hardware. Short open-loop segments of a learned policy ran on the robot and exposed repeatable, localized power-delivery and sensing gaps. Closed-loop on-robot execution is the next stage.
The passive spine couples two almost biped-like halves - cat-like geometry, weak actuators and energy-aware operation shaped every choice. Next: power, sensing, momentum, and deeper use of passive mechanics.
## Models
Self-contained MuJoCo models (primitive geometry, no external meshes):
- `sim/models/mao_real_v16_fix.xml` — current real robot: rebuilt front shoulders, rigid multi-box feet, FSR slide joints.
- `sim/models/mao_real_v14_socks.xml` — passive two-segment "sock" feet variant.
- `sim/models/mao_real_v15_head_tail.xml` — adds a 3-DOF head/neck and a 4-segment cable tail.
### Run
View a model in the interactive MuJoCo viewer (local machine, needs a display):
```bash
pip install mujoco
python sim/test_sim.py
```
Rotate/zoom with the mouse. To switch models, edit the path at the top of `sim/test_sim.py` (defaults to `mao_real_v16_fix`).
**Pose inspector** - load named poses (cloud / sit / stand / play-bow) and print joint & sensor tables:
```bash
python scripts/v16/mao_pose_inspector_v16.py # or scripts/v15/... , scripts/v14/...
```
In the viewer: number keys switch poses, `P` prints all joints, `T` dumps a target snapshot, `N`/`B` toggle front/rear servos, `R` reset, `Space` pause. Run from the repo root.
## License
- **Code** - [MIT](LICENSE)
- **Media, CAD & docs** (photos, videos, 3D models, documentation) - [CC BY 4.0](MEDIA_LICENSE.md)