Six robots, one brain
Chris: "I bet I will come up with a few different hardware platforms. Two-wheel drive, four-wheel drive, tank drive, taller, shorter, wider." A review of the code found the robot's size written out in at least six places, two wheel joints hard-coded, sensing windows sized for one body, one blade assumed everywhere, and no way to tell the simulator which robot to run. So we fixed all of it the practical way: by designing five more robots and making the whole stack run each of them.
The six
| Drive | Size (L × W × H) | Cut | For | |
|---|---|---|---|---|
| Ion | two wheels + caster | 61 × 60 × 35 cm | 36 cm | small and medium lawns; the one we're building first |
| Quark | two wheels + caster | 46 × 42 × 27 cm | 24 cm | side passages and small lawns |
| Photon | Ion + 1.1 m mast + solar roof | 61 × 60 × 115 cm | 36 cm | long grass, low hedges, no powered dock |
| Vector | four-wheel skid-steer | 82 × 74 × 42 cm | 46 cm | rough ground, roots, molehills |
| Graviton | tracks | 86 × 76 × 45 cm | 46 cm | slopes, soft and wet ground |
| Tachyon | two wheels + 2 casters, twin blades | 95 × 100 × 40 cm | 70 cm | big open lawns |
The names come from physics, with a future-tech feel; Chris asked for cool ones. Each one has a battle card, a 3D render, drawings, specs, a parts list and a build plan on the new Platforms page. Only Ion is being built; the other five are first-pass concepts, simulated but not built, and their prices are estimates.

One file per robot
Each platform is one YAML file: the body, the drive (type, wheels per side, track width, how much it slips turning), the blades, where each sensor sits, the battery, and a design sheet. A small pure-Python module loads it and works out every number the software needs. Nothing robot-sized is written anywhere else any more:
- the planner takes its stripe spacing, edge lap, stripe-end clearance, turning clearance, raised-bed margin and footprint from it;
- the safety gate takes its corridor, pivot circle, ground windows and obstacle ceiling (a 1.15 m robot must see a branch at 1.3 m);
- the power model scales rolling resistance with mass, turning scrub with the drive, blade power with cut width;
- the simulator model is generated from it: xacro reads the YAML, so a four-wheel robot gets four drive joints and a twin-blade robot two blade motors with no new XML;
- the reports judge each run with the robot that ran it, which every node now logs.
make mow-run ROBOT=vector runs the whole stack on Vector. For Ion, every number came out exactly as before; tests
pin that.
What the other bodies found
The planner's "body stays on the lawn" test, from the corner fix, now runs for every platform. It failed on its first run, in three ways:
- Skid-steer and tracks turn about the middle of the body, so the tail swings out as far as the nose. Ion turns about its rear axle, and its long end swings away from the edge. Vector and Graviton swung their tails 14 cm over the edge at every corner. Now, where Ion's corner doesn't fit, the robot first eases inward to where a turn does, then turns and merges back out.
- Tachyon's 0.80 m nose went 39 cm over while angling back out onto the lap, and 3 cm into a bed while sliding between the two laps round it. Merges can now be longer, and the slide is as long as the nose needs (Ion's stays 2 m; Tachyon's is 6 m).
- Where nothing fits (a side too short for the body to stop, turn and merge), the whole lap moves inward until every corner fits. The body never goes over the edge; the price is an uncut strip, which the coverage report shows. This one also caught a bug in Ion's own plans: on the Test patch, the south-west corner next to the flowerbed's margin had swung Ion's body over the zone's edge in every run. That lap now runs 5 cm further in.
Moving the whole lap is blunt: for Tachyon in the back yard, one tight corner moves it in 40 cm all the way round. Moving in only the sides next to the bad corner is on the list.
Smoke tests, not 30-minute runs
Every platform needs checking whenever the code changes, and the full regression set takes hours. So each gets a smoke
test instead: make smoke ROBOT=<name> undocks, mows a 5 × 4 m patch in front of the dock at full simulator speed, and
docks. make smoke-all does all six. A report checks that every node agreed which robot it was, the model has the
profile's joints, the mission docked, there were no false stops, and the coverage report's checks pass. It also checks
turn calibration: during turns in place, the real yaw rate over what the wheel odometry believes. Skid-steer and
tracks slip sideways to turn, so the odometry uses an "effective track" (track × a scrub factor). 1.0 means that guess
matches how the simulated body actually slips.
| Robot | Reachable coverage | Turn calibration | Result |
|---|---|---|---|
| Ion | 99.1% | 1.00 | PASS |
| Quark | 99.2% | 1.00 | PASS |
| Photon | 99.3% | 1.00 | PASS |
| Vector | 98.0% | 0.99 | FAIL: mowed the patch, then failed to dock three times |
| Graviton | 99.0% | 1.11 | PASS |
| Tachyon | 0% | none | FAIL: never got off the dock |
Four of six pass first time (smoke-4, about two minutes each). Graviton's tracks turn 11% faster than its odometry
thinks, inside the ±25% the test allows; its scrub factor gets a nudge when there's a real one to measure. The two
failures are both at the dock, not the lawn: the undocking and docking moves still assume Ion's size. That's the next
post.
The first full set caught something no unit test would: Quark never left the dock. The safety gate stopped it 27 times for an "obstacle 1 cm ahead", a 30 cm-wide something 15 cm up that moved with the robot. It was Quark's own deck. Its 3D lidar sat 4 cm above the deck, so the lowest beam (−25°) skimmed the deck's front edge (−26.6°) and returned points on it, right at the nose. Ion's lidar sits level with its deck top, so the beams that hit Ion's deck do it inside the lidar's 10 cm minimum range and are dropped: lucky, not designed. Three fixes: Quark's lidar moved up 3 cm; a profile now fails validation if its lowest beam is within 5° of its own deck edge; and the gate's forward check ignores returns within 3 cm of the body, as its turning check already did.
Before any of that, make test checks each profile without a simulator: it validates, renders to a URDF that matches
it, the planner keeps its body on four lawns and out of a bed, and the gate and power model follow its body.
Battle cards
Chris asked for "pictures and all. Battle cards. Something cool." So the Platforms page now deals a card per robot: a 3D render, a class (Scout, Brawler, Climber...), stat bars, a special and a weakness, and the latest smoke-test result. A head-to-head picker compares any two. Each robot also has its own page, with a 3D view you can spin.
The renders are generated from the same YAML the simulator reads, not drawn: a small three.js model puts the wheels,
tracks, casters, mast, solar roof and sensors where the profile says they are, and a headless browser photographs it
(scripts/render_platforms.sh). The shell's shape and colours are illustration; the positions aren't. Most stats are
computed too: cut width, top speed, the turning circle, the lidar's blind zone. Range, slope and cost are the design
sheets' estimates, and the page says so. Making the model also caught a real bug: Tachyon's deck stuck 5 mm out of its
own footprint, which a new test now checks for every robot.
What this doesn't prove
The simulated skid-steer and tracks are approximations: a track is three road wheels, and slip is whatever the physics engine does with rubber-ish friction. The scrub factor is a guess until a real one is measured. And smoke tests check that each body works end to end, not that it's safe: the full safety and coverage set still runs on Ion only.