My Robot Was Tipping 13 Degrees. That Was the Whole Result.
Yesterday I published a post about a wheel-slip detector that was accurate and useless. Today I have to explain why that post now carries a correction at the top, because the thing I spent months measuring was being manufactured by my own robot.
It started with a sensor I had never used
The study had ended on a wall. Wheel encoders and an IMU could not tell a robot stuck with its wheels spinning from a robot driving normally, at 0.552 balanced accuracy against 0.500 for a coin. The obvious next step was a channel that measures body velocity without asking the wheels, and the simulation had a lidar that four months of work had never touched.
So I probed it. In a world containing nothing but three flat ground boxes, the scan came back with 168 of 360 rays hitting something 0.68 m ahead.
I drove the robot forward 1.16 m. The surface stayed at 0.70 m.
The rays were describing the floor
The pattern was the tell. Straight ahead 0.68 m, at 45 degrees 1.00 m, at 75 degrees 2.62 m, and nothing at all at 90 degrees. That is not a wall. A wall at constant distance would return finite ranges out to the sides. This is what a scan plane grazing the ground produces, and the ratios give the angle: about 15 degrees below horizontal.
The lidar is bolted to the robot, so if the beam points down, the robot is pitched. Three independent channels agreed, and they are not derived from each other:
| Channel | Reading |
|---|---|
| Ground-truth pose from the simulator | pitch +13.30° |
| IMU orientation | pitch +13.30° |
| IMU accelerometer, x axis | −2.25 m/s², which is −g sin(13.3°) |
Constant to the second decimal, at rest, at 0.2 m/s and at 0.4 m/s.
Then the robot description explained it in one line of arithmetic. The drive wheels sit on the centre axle. The only caster is at the rear. Nothing carries the front half of the chassis, so it tips forward until the front bottom edge rests on the ground, at an angle of atan(0.033 / 0.1435) = 12.96 degrees. Wheel radius over half the chassis length. The measured 13.30 was the geometry, not a mystery.
A front caster fixes it. Ground truth and IMU orientation now read 0.000 degrees, the accelerometer reads −0.002 m/s², and the lidar returns nothing at all in that world, which is the correct answer: three coplanar ground boxes contain no vertical geometry. Every one of those 168 rays had been the floor.
What it cost
Same world, same commanded speed, the only change being a level robot:
| Robot | Distance travelled | Odometry reported | Error |
|---|---|---|---|
| Tipping 13.3° (all earlier work) | 6.44 m | 9.61 m | 49.4% |
| Level | 9.64 m | 9.64 m | 0.00% |
The slip was gone. Not reduced, gone. A friction coefficient of 0.02 does not make this robot slip at 0.4 m/s. What made it slip was the chassis dragging: the drag demanded more traction than the surface could supply, so the wheels turned while the body stayed behind.
Everything downstream was measured in that state. The filter that recovered 13 mm. The detector that lost four ablations out of four. The observability result at 0.552. All of it describes a robot ploughing the ground with its nose.
Why no ablation caught it
This is the part worth keeping. Every control arm was run correctly. Constant-speed baselines, leave-one-terrain-out, a trade-off curve, a dominance test against measured arms rather than interpolated ones. The discipline was fine.
It could not have caught this. An ablation compares two arms against each other, and both arms ran on the same rig. When the fault is in the substrate that every arm shares, comparisons between arms are blind to it by construction. Four months of careful controls never once measured the robot's own posture, because nobody thinks to.
The tell was there the whole time, in a comment I had written myself: the contact sensor reported "touching" in every run, and I had attributed that to sensor semantics. The body was touching the ground. It was reporting correctly.
Getting slip back, honestly
With the robot level, flat ground would not produce slip at any friction I could set, and the physics says why. A free-rolling robot at constant speed on level ground asks almost nothing of the surface. No traction demand, no slip. The dragging chassis had been supplying the demand by accident.
A slope demands traction continuously, and the threshold is known before you run anything: the wheels break loose when tan(angle) exceeds the friction coefficient. For 0.02 that is 1.15 degrees. So I built ramps at 2, 5 and 10 degrees, and ran every one of them twice, once at friction 0.02 and once at friction 1.00. The high-friction ramp is the control. If the robot cannot climb that either, the problem is torque, not slip, and nothing can be reported.
| Slope | Low friction (0.02) | High-friction control (1.00) |
|---|---|---|
| 2° | 2.79 m travelled, 245.2% odometry error | 9.61 m travelled, 0.34% |
| 5° | 3.76 m, 156.1% | 7.35 m, 30.9% |
| 10° | 3.60 m, 165.8% | 6.67 m, 44.7% |
Only 2 degrees is a clean measurement. There the control ramp climbs perfectly, so torque is sufficient and friction is the only variable left, which makes that 245% pure slip. At 5 and 10 degrees the control arm degrades too, so torque limits are mixing in and those numbers cannot be called slip. Without the high-friction arm I would have reported all three.
One earlier result survived the move. On the clean ramp, wheel odometry is off by 6.852 m and the IMU-fused filter is off by 6.843 m. Nine millimetres out of six and a half metres. Different rig, same answer: neither channel observes the slip, so the filter has nothing to correct with.
What I do differently now
Measure the rig, not just the treatment. There is now a script that checks the robot's posture, the gravity leak in the accelerometer and the lidar's view together, and it runs before a measurement series rather than after a confusing result.
Distrust your own summaries. On the way to this, my summary script reported the detector 19.9% ahead on one comparison. The number was computed correctly and it was an artefact of interpolating across a near-vertical segment of the curve. A measured arm at the same distance was four times better. The summariser is a measuring instrument too.
Correct in public. The previous post stays up with a correction at the top rather than quietly disappearing, because the deletion would hide the only genuinely useful part: a study can be run with good controls, honest ablations and careful statistics, and still measure the wrong thing, if nobody checks what the apparatus is doing.