The banana floated over the hills
A 30-centimetre player broke the camera and the ground collision, and a generated jungle corridor pushed us to put the level editor inside the game.
The banana is 30 centimetres tall, and the camera and the ground collision had been written for a person.
At the end of May the generated level lost to a level editor.

Drag the slider: the same game in May and in September, with the level editor in between. The first camera sat 3.5 metres behind the banana. In body lengths that is the same as a normal third-person camera about 21 metres behind a person, and the character took up about seven percent of the screen.
Thirty centimetres changes the controls
One world unit is one metre. The player is 0.30 metres high, a crate is about one metre, the picker 1.8 and a palm around 12. It sounds like a modelling note, but it touches nearly every system.
Each wrong value came from someone doing a sensible thing. The camera author used familiar third-person distances, the locomotion author used familiar running speeds and the level author used metres. Only playing the combination exposed the mismatch.
The running speed was the same mistake: 4.8 metres per second is a normal shooter speed, and for a 30-centimetre banana it is sixteen body lengths every second. Episode 7 tells that story. Here I stay with the camera, the ground and the editor.
The terrain was lying
At the end of May the visible ground was hilly, while the physics ground was a flat box at height zero. In hollows the banana floated. On rises it sank into the soil.
The first fix was a collider built from the same terrain height function as the visible mesh. It was deliberately coarser than the picture, and that turned out to cost something: later, on a sculpted hill, the banana’s feet floated above the visible ground again. That is fixed now, and the floor you touch is the floor you see.
Why the level editor became part of the game
The first tech-demo level was a long jungle corridor generated from code. It gave us something repeatable quickly, which mattered while combat was unfinished. It was also a poor way for me to direct a scene. To move a bend, open a view or place cover, I had to describe the change in an issue, wait for code, compile, and then explain why the result did not match the picture in my head.
On 12 June I compared the result with small Bevy environment demos and rejected the vegetation and layout. I needed to set the width of a combat lane, put cover at banana height, reveal landmarks from chosen sightlines and decide when a human-sized prop blocks or opens a route. Writing that into issues was slower and less precise than doing it directly.
An editor already existed as a development tool, but I could not rely on it. It crashed when selecting some objects, the asset library ignored .gltf files, and a grass system generated 250,000 wind-driven blades in the main menu before any level had loaded. It also had to save the same level document the game loads, because a separate scene format would have meant two versions of every feature.
So the editor stayed inside Bevy and inside the game repository. It uses the same lighting, terrain, weather and physics as play mode, and it is compiled out of release builds. The next two months were a mix of tool work and game work, because the editor was the only way I could design the game directly.
Under the hood: the terrain collider
What went wrong later. The first collider took one sample every 75 centimetres instead of every 25. At the player’s speed and the fixed physics rate that still gives several steps per triangle, and it cut collision work. A comparison test samples both the terrain function and the collider, so a later change cannot quietly pull them apart. But on a sculpted hill the 75 cm chords no longer followed the folds of the ground. Feet sat up to 15 centimetres above the visible terrain standing still on an 11-degree slope, and up to 21 centimetres while walking. That was the “floating” I reported on build 2476. The collider now uses the same grid as the visible mesh.
The collider. The trimesh is built from the same TerrainSurface sampling as the visible mesh (ADR-0021, 2 June), with FIX_INTERNAL_EDGES to reduce ghost collisions on interior edges:
Some(Collider::trimesh_with_config(
vertices,
indices,
TrimeshFlags::FIX_INTERNAL_EDGES,
))
The ADR compares three options. A trimesh is hollow, so fast bodies could tunnel through it, and its BVH is more expensive (4,608 triangles at the original 48x48 subdivisions). It is static, so the BVH is built once at load, or when the terrain changes in the editor. Tunnelling was judged unlikely: the ADR puts the player’s speed at about 10 m/s at most, and the physics runs at 64 Hz, so about 0.16 m per tick, against a capsule radius of 0.10 m. A respawn_if_out_of_bounds check at y < -1.0 is the backstop. When the surface is flat and has no brush strokes the code keeps a cheap Collider::cuboid instead.
Why not the heightfield. Avian’s heightfield is cheaper and has no ghost edges, but its internal triangulation does not match the visual mesh vertex for vertex, and the row/column to X/Z mapping can come out mirrored. The terrain data was not stored in a normal image grid, and mixing up rows with columns, or X with Z, would have produced a valid collider turned the wrong way. Building the same vertices and indices as the visible surface made that kind of mistake easy to see.
Fixed step. Input is collected in the frame loop. Movement that changes rigid bodies runs in Bevy’s FixedUpdate, and Avian 0.7 solves them in FixedPostUpdate. That keeps a fast PC and a slow PC from producing different jumps or collision responses. It is easy to violate in an ECS: a system can compile in the wrong schedule, or read a velocity before Avian has solved contacts. So the project uses typed system sets across crates, and tests that run the relevant schedule instead of calling a movement function in isolation.
Next time: the level editor inside the game, and a hill that looked flat.