the symptom, in your words

"Navmesh openings erode by whole cells against agent radius"

✓ verified on 26.08.05
lane Writing gameplayposted

▸ SYMPTOM

  • A generated building's upper floors are reachable on some units and not others at the same seed, with the same template and the same stair column. The failing set changes when the seed changes.
  • It reads as a bug in the generator's placement decisions.
  • It is arithmetic in the navmesh voxeliser, not a placement bug.

▸ CAUSE

The navmesh voxeliser erodes every walkable surface away from obstacles by the agent radius, rounded up to a whole cell, from both sides.

An opening of clear width W leaves W - 2 * ceil(agentRadius / cellSize) * cellSize of navmesh. If that remainder is under one cell, no navmesh crosses the opening at all.

Everything a body passes through, squeezes between, or ducks under is subject to the same sum against agent radius, agent height, and agent step. A door width chosen because it looks like a door is a coin flip.

▸ FIX

Derive the clearance minimum from the agent. Do not author it by eye.

  1. Write the floor as 2 * agentRadius + 2 cells.
  2. Put the measurement that produced the cell size in a comment next to it.
  3. Lint the art library so no asset can quietly go under that floor later.

Treat one surviving cell as a failure, not a pass. A connection that survives by a single cell survives because of where it fell on the lattice. It works in the test scene and breaks the day something moves. Budget two spare cells, then prove it by moving the lattice and regenerating.

Find the cell size by sweeping when the engine will not tell you. Sandbox.Navigation.NavMesh.CellSize and CellHeight exist and are documented, but neither is on the game assembly's access list, so game code cannot read them. Step the opening width, regenerate, and find the width at which the connection appears. Measured here, the connection appeared between 39.37 and 40.16 units at a 16-unit agent radius. That is exactly 2 * 16 + 8, which puts the cell at 8 units. A sweep against the real build beats a number read from a header.

▸ WHY IT WORKS

Build a chain probe before the third hypothesis. Replace a pass/fail reachability probe with one that samples the intended route waypoint by waypoint. It asks for a path between each adjacent pair.

That turns one bit ("did it get upstairs") into an address ("the break is between the front door and the hall on one parcel, and between the stair foot and the first tread on another"). Three separate defects that had been averaging into one confusing number separate immediately. A plausible lattice-alignment theory can eat hours first, and it is a symptom, not a cause.

Verified on engine 26.08.05: seen in a real project.
s&box moves fast; an undated fix is a liability. Spot a stale detail?
changelog
  • Published

Want to know when new guides or fixes drop? Join the community to help build this out. Report gotchas, flag outdated fixes, or just lurk.

Join the Discord