What this is: The story of building six indoor simulation worlds — corridors, rooms, an apartment, an open hall, and a deliberate crash-test maze — for the A.1 training sprint, each paired with a perfectly matching ground-truth map.
Why it’s here: Training a drone to navigate indoors only works if the simulator’s geometry and the “answer key” map agree down to the centimeter. This log explains how we guaranteed that agreement by design, not by luck.
Date: 2026-06-15 Ticket: worlds_a1 (6 scenes, part B) — handoff from the rl-lab team, green-lit by Aleks
Glossary
Before diving in, a few terms in plain English:
- Gazebo — the 3D physics simulator where our virtual drone flies. Think of it as a video-game engine, but built for robots: gravity, collisions, and sensors all behave like the real world.
- SDF (Simulation Description Format) — the text file that tells Gazebo what a world contains: where the walls are, how big the floor is, what the drone looks like. It’s the blueprint the simulator reads.
- Scene / world — one self-contained environment the drone trains in. We built six of them.
- A.1 — the name of this training sprint. Each sprint has a list of scenes the drone must learn to handle; A.1’s list had six.
- Occupancy map — a top-down grid where every cell is labeled: free (you can fly here), wall (solid, don’t), or unknown (sealed off, never visited). It’s the ground-truth “answer key” the learning system checks itself against.
- Scene geometry — the actual shapes in a world. We describe every wall, partition, and column as a box (a rectangular block), optionally rotated. Even round rooms and zig-zag corridors are built from many rotated box segments, like bricks following a curve.
- Footprint — the 2D shadow a world’s walls cast when seen straight down. The whole point of this work is that the SDF footprint and the occupancy map’s walls are the same shadow.
- Spawn point — where the drone first appears in a world. We measure “reachable space” by walking outward from here.
1. The task
The rl-lab team handed over a spec for six scenes the A.1 sprint needed. For each scene, the simulation side had to deliver three things:
- A Gazebo SDF world — the flyable 3D environment.
- A ground-truth occupancy map at 10 cm resolution, with every cell marked free, wall, or unknown.
- A meta file recording the world’s origin, its size, its name, and where its doors are.
Two hard constraints came with it: every doorway had to be at least 1.4 m wide (so a drone can actually fit through), and the wall footprint had to match the rl-lab team’s 2D version cell-for-cell. That last point matters more than it sounds — if the simulator and the map disagree about where a wall is, the drone learns from a lie.
2. The approach: one source of truth
The trap here is obvious once you’ve been bitten by it. If you draw the 3D world by hand and draw the map by hand, the two will drift apart. A wall nudged 20 cm in the SDF but not in the map, and suddenly the drone “crashes” into empty air according to its training signal.
So we refused to draw them twice. Instead, a single generator script describes each world as one list of boxes — walls, partitions, columns. Round rooms and the zig-zag corridor are just lists of rotated box segments. Everything downstream is derived from that one list:
- The SDF is built from a known-good template (GUI, physics, plugins, plus a
spherical_coordinatesanchor that ArduPilot’s orientation system needs — leave it out and the flight controller’s estimator hangs), then a floor, then one model per box, then the drone spawn. - The occupancy map is rasterized from the same boxes: each box (including its rotation) paints its cells as wall; a flood-fill outward from the spawn cell marks everything it can reach as free; whatever’s left over and not a wall becomes unknown (sealed-off pockets the drone can never enter).
- The meta file and a preview image fall out of the same data.
Because the SDF and the map are computed from one box list, they cannot disagree — there’s nothing to drift.
The grid convention
To keep everyone aligned, the grid follows a fixed convention shared with the rl-lab team’s config: 10 cm cells; the map’s origin sits at the south-west corner of the world; the X axis points east, Y points north, and row 0 of the map is the southern edge. Walls are 10 cm thick. This is the contract that lets a map drawn in the sim line up with a map drawn in the lab.
3. The six scenes
Here are the worlds we shipped. The sizes are physical dimensions in meters; the cell counts are the occupancy grid.
| id | size (m) | grid | free | unknown | wall | doors |
|---|---|---|---|---|---|---|
| corridor_straight | 10×2 | 20×100 | 1862 | 0 | 138 | — |
| corridor_L | 8×8 | 80×80 | 2485 | 3600 | 315 | — |
| two_rooms | 11×5.5 | 55×110 | 5685 | 0 | 365 | 1 |
| apartment | 12×10 | 100×120 | 11418 | 0 | 582 | 3 |
| open_hall_columns | 14×12 | 120×140 | 16104 | 0 | 696 | — |
| crashtest_zigzag | 18×10 | 100×180 | 5466 | 11516 | 1018 | 2 |
A useful sanity check: for every scene, free + unknown + wall equals the total number of cells in the grid. No cell goes unaccounted for — verified across all six.
A quick tour:
- corridor_straight — the simplest case: a 10-meter hallway. No unknown cells, because there’s nowhere sealed off.
- corridor_L — an L-shaped hall. The 3,600 unknown cells are the square notch cut out of the L’s inner corner.
- two_rooms — two rooms joined by a single centered doorway.
- apartment — the most realistic layout: three rooms, three doors, two interior columns.
- open_hall_columns — a wide-open space dotted with five columns to fly around.
- crashtest_zigzag — the torture test: a sharp zig-zag corridor connecting two round rooms, with the unknown cells being everything walled off outside the path.
4. Validation
We checked the worlds four different ways before calling them done.
Syntax check. Running Gazebo’s built-in validator on all six SDF files returned VALID. (There’s one cosmetic “error” about not finding the drone model’s URI — that’s a known limitation of the standalone checker, which doesn’t register the file-lookup callback. The exact same message appears on our reference apartment world. Geometry and syntax are fine.)
Visual check. We rendered a preview image of each world and eyeballed it: the L-shaped notch shows up correctly as unknown space in the corner; the apartment has its three rooms, three doors, and two columns; two_rooms has its centered door; the open hall has its five columns; and the zig-zag shows two round rooms with four visible bends and a clear path connecting end to end.
Connectivity check. Because free is defined as “reachable from the spawn point,” every world is guaranteed to have exactly one navigable region — no accidental islands of free space the drone can’t actually get to. For the zig-zag, this confirms all four bends and both rooms are genuinely reachable.
Live smoke test. With Aleks’s go-ahead and a free test bench, we launched the two riskiest worlds headless in the real simulator:
- apartment loaded all 12 models (floor, four perimeter walls, three vertical dividers, two horizontal dividers, two columns) plus the drone — 61 entities reported in the world pose feed, and every drone sensor topic present and publishing (the distance sensors, the downward rangefinder, the scan/sweep streams, the servo command, the IMU, and joint state).
- crashtest_zigzag loaded 62 models — 60 boxes including every rotated corridor segment and the rings of the round rooms, plus floor and drone — with 11 world topics live.
The logs were clean on geometry. The only warnings were a frame-ID note that lives in the drone model rather than the world, and a graphics-library fallback that’s normal for headless rendering — neither touches geometry or physics. Teardown was clean: no orphan processes left behind, and GPU memory returned to its baseline. We ran exactly one launch cycle per world on purpose, to avoid a known headless-rendering crash pattern that builds up over repeated cycles.
5. A note on parity
One scene came back from the rl-lab team for a size correction. The crash-test zig-zag was originally specced at 12×10 m, but the spec itself had flagged that bounding box as “approximate — sim will refine it.” And indeed: at 12 m, the zig-zag’s sharp turns plus two round rooms simply didn’t fit. We grew it to 18×10 m, and the rl-lab team updated their config to match. The other five scenes matched the original spec exactly. A full parity manifest documents the mapping for the lab.
6. The camera redo
Here’s the most human part of the story. The first batch of worlds passed every automated check — but when Aleks opened them in the visual editor, they were stuck flat, top-down, with no way to orbit or zoom. His verdict was blunt and fair: useless to inspect.
The cause was a copy-paste habit. We’d lifted the GUI block from our earlier room worlds, which pinned the camera straight down and shipped no camera-control plugins — so the view was locked overhead. Our known-good reference world had no GUI block at all, which lets Gazebo fall back to its default free orbital camera. The geometry was never wrong; only the viewing setup was.
The fix: drop the GUI block entirely so the default camera takes over. While in there, we also matched the reference world’s look — walls shortened to 2.5 m and slightly thickened, plus proper materials (grey floor, light walls, a red marker on the north side for orientation, sand-colored partitions, brown columns). The wall and free-cell counts were recomputed, but the map origin and the cell formula stayed put, so parity held.
The zig-zag needed a second pass too: the new thicker walls had pinched its narrow corridor shut, cutting off the far room. We rewrote the corridor walls as continuous offset polylines with mitered corners — no gaps, no pinch — and gave the round rooms straight “necks” sized to the corridor width so they join cleanly without leaks. After this, a fresh flood-fill confirmed the space was properly sealed, with all four bends preserved.
On the second smoke test, Aleks opened all six worlds one at a time in the GUI and confirmed: the camera orbits freely in every direction, and the worlds look right. Teardown stayed clean between worlds.
7. Files and what’s next
The work produced a new world generator, the six worlds (each with its SDF, occupancy map, meta file, and preview image), and the parity manifest for the rl-lab team. No build-config change was needed — the worlds directory installs wholesale, so the new folder is picked up automatically via the symlink install.
What’s still ahead: the rl-lab team will run a parity comparison of the masks before training begins and finalize the corrected zig-zag size in their config. Per Aleks, we’ll run a quick live check of one or two worlds on the bench before marking the milestone done. Mission overlays on top of these worlds are the rl-lab and Aleks side of the house, not simulation’s.