Lucid Lake Support

Blog#snoot

Engineering a plush robot before it exists

Snoot is a companion robot inside a plush polar bear. There is no physical prototype yet; everything below is simulation only. No motion clip gets rendered until it passes three checks: the servos' joint limits and speed, a torque model, and a clearance check against the plush skin.

Simulation buys speed: change an arm, re-run every clip, see the margin in minutes. The cost is that every number rests on a model, and our fabric and heat models are the weakest. We name the model behind each number.

In a re-run on 13 Nov the performer's 33 clips all pass. The worst clip reaches 0.80 of the torque-plus-speed line, which sits at 0.9. The highest torque is 66 % of stall, against a 70 % limit. The closest the arm comes to the head's fabric is 11.3 mm, against a 2 mm rule.

One robot, four descriptions

The robot exists as CAD, a joint spec with its URDF, a MuJoCo model and a Blender rig for the plush look. The firmware's C tables are a fifth copy. If two disagree, a test passes on a robot that doesn't exist.

A pipeline diagram: CAD feeds the joint spec and URDF, which feeds MuJoCo, the Blender rig and the firmware C, with a check on every arrow
One robot, four descriptions, and the check between each pair
  • One datum. Millimetres, the soles at z 0, x forward, y left. Every position names its frame. The render frame put the shoulder axis at z 191.3 mm, the spec at 185.0 mm, so paw targets are taken relative to the shoulder.
  • One source for joints. The URDF is generated from the spec. MuJoCo loads that URDF, and the firmware's joint tables are generated from the same spec. Masses come from a mass budget re-based on the CAD: the performer's URDF weighs 1,475.6 g.
  • Check the copies against each other. The Blender rig is posed from clip files and measured against the URDF. That check caught an unkeyed joint holding the last clip's pose, 38.7 mm and 16° off. Now every joint is keyed on every frame. On the bodies we checked, the rig matched to 0.000 mm and 0.031°.

The elbow shows why the mirror rule matters. The left elbow's axis is turned 45° about z, to (0.7071, 0.7071, 0), so a bent forearm crosses toward the belly. In the arm study that let every act reach at the existing 25° shoulder stop, for 0 g. Our rule for pure axes, negate and swap the x and z limits, fails for a skewed axis. A rotation axis is an axial vector, so under the left-right mirror (ax, ay, az) becomes (−ax, ay, −az), with the same limits. The right elbow is (−0.7071, 0.7071, 0). The left and right paw sweeps must match, which is how we check it.

MuJoCo as the test bench

What the model has:

  • Servos with stall torque and no-load speed at their supply; torque falls as the joint speeds up. Rotor inertia is an estimate.
  • Masses from the mass budget, per link.
  • Contacts between links, except pairs that overlap at rest.
  • The post. The performer stands on a hidden post in its charging base, so it needs no balance policy. On the post it holds and sways 0.33°.

Damping is a guess, so we run two models. The motor-slope model takes the servo's own torque-speed slope, 0.046 N·m·s/rad, and gates the clips. A 2× damped model, 0.1 N·m·s/rad, is a bound we always report. In the band songs the drum crash reaches 0.75 of the force range on the motor-slope model and 1.00 on the 2× bound, against a 0.9 line.

What the model doesn't have:

  • Fabric. The skin is a fixed shape. The arm's fabric must stay at least 2 mm off the head's fabric. Its centre line must stay 20 mm off the head's skin.
  • Plush compression. We assume about 5 mm of compressed pile. We don't simulate it.
  • Backlash. The servo bench will measure it, and then it goes into the model.
  • Real thermal paths. We use an estimated heat model. On it, two clips looped nonstop for an hour in a 30 °C room cross our 55 °C line. The mic held out reaches it in 17 minutes. A body shiver reaches it in 11. No show loops a clip for an hour, and a continuous show beat is capped at 2 minutes. But the model is an estimate until a servo is measured inside the fur.
The performer's joints as plain boxes in MuJoCo, standing on its post, mid-shrug
Simulation: the performer mid-shrug on its post (the post drawn in). Not the plush look.

Firmware in the loop

Unit tests check one rule at a time; real traffic finds the interactions. So the head's ESP32-S3 safety code, in C, runs against a mock servo bus with MuJoCo behind it.

For the walking study, a stand-in for the Linux board runs the trained policy, exported to C, and the C walk task sits between it and the simulated servos. A single run on 13 Nov walked 30.9 s at 7.12 cm/s with no fall.

Three rules run on every tick.

  1. Freshness by echo. A target counts only if its CRC holds, it echoes one of the last 5 state frames the S3 sent (100 ms), and it is newer than the last one taken. Anything replayed, stale or garbled is treated as lost.
  2. Clamp after rounding. Every goal is clamped to the joint limits and to the knee-by-hip rule. It is rate-capped at 0.85 of no-load speed. Then it is checked again after rounding to servo counts, because rounding can move a goal past a limit. If the knee can't clear the hip's rule, the hip is held back.
  3. A misbehaving peer. 7 kinds of bad input, 2,000 ticks each: random bytes, replays, stale and future sequence numbers, out-of-range and NaN targets, wild valid frames. No goal leaves the clamps or beats the rate cap.

A test counts only if it can fail, so each safety suite carries mutants: copies of the C with one guard broken. The performer's has 8. In this one from the 13 Nov re-run, the stall line, 0.90 of each servo's own torque limit, becomes a fixed 0.85:

mutant fixed_stall_line: 2 fails in C., first: C. a soft (35 %) servo jammed at its limit trips in 0.5-0.7 s (None)
M. mutants OK

C. and M. are the suite's section labels. None means the jammed servo never tripped: softened to 35 %, it can never reach 0.85. The test caught it.

Locomotion, and the misses

First, which body is which. The performer stands on a hidden post and does not walk. Walking was studied on a separate walker variant: 16 servos, legs 1.5 times the length first drawn, 1,836 g. Every walking number below comes from that walker in simulation.

Our first gaits ran open loop on earlier bodies, and they were fragile. One three-step clip ended turned 35° right; excluding 4 more contact pairs made it 47°. Steering trims jumped between −47° and −5° for small changes. A 10-servo waddle made 0.9 cm/s and drifted 112 mm sideways in 7.4 s. We stopped quoting open-loop speeds.

On the walker, a learned policy works in simulation:

Test (walker variant, simulation)Result
Straight walk, 7 seeds, 30 s each6.2–6.5 cm/s, no falls
Sideways drift, heading2.6–10.1 % of distance, within 5°
Swing lift23.6 mm median
Servo use≤ 0.88 of the torque-speed envelope (line 0.9)
Disturbances, 5 seeds eachno falls at 20 ms latency, 4 N pushes, 5° slopes, ±20 % mass
Harder pushesfalls at 8 N

Steering is a near-pass. The walker takes 0–6 cm/s and ±15°/s and turns on the spot with no falls. But spinning, it drifts 1.04 cm/s against a ±1 cm/s line; its hip load is 0.351 of stall against 0.35; and it falls 3 times in 5 across a 5° slope.

Two misses taught us the most.

Contacts hide in exclusions. On an earlier sitting body, the arm hung over the leg's box. The easy fix was a rest margin on the excluded contacts. Review showed it would have hidden arm-through-leg poses in the motion clips. So we moved the arm 6 mm out, 2 mm clear of the leg, and let the contacts stay live.

The fall check had to use the sim's tilt. The firmware raises FALLEN past 40° of tilt, but not inside the rest sequence, the slow sit before torque-off. Our first sweep counted only FALLEN, and called a knee jam a near-fall when the walker tipped 52°. The test now counts falls from MuJoCo's own tilt and prints each run's maximum. The 13 Nov re-run still shows one open gap: after torque-off, the limp walker rolls back off its sit, from 5° to 99° of tilt. No limp pose holds yet.

None of this has touched hardware. The servo bench comes next: measured servo numbers replace assumed ones, and every result above is re-run.

This post is about Snoot: A plush bear that performs. It sees you, talks, and makes a show out of your day.

Aptmark is private

Enter the password, or ask me for access.

Ask for access