the symptom, in your words

"Derive procedural character geometry from the collision surface, not the render mesh"

✓ verified on 26.07.15a
lane Writing gameplayposted

▸ SYMPTOM

You auto-place character-facing geometry against procedural terrain — climb grips on a cliff face, node standoffs, body-clearance points — by sampling the terrain height. The grips look right in the editor but sit up to ~0.6 m off the real surface at runtime, and a surface-proximity audit reports every node failing its tolerance.

▸ CAUSE

A voxel or heightfield world typically carries two different surfaces:

  • Render surface — fine cells (e.g. 0.2 m cells, 0.25 m steps, per-cell skirt quads). This is the pretty geometry you see.
  • Collision surface — a coarser per-block heightfield, where each block takes the block's maximum cell height (e.g. a 4×4-cell reduction → 0.8 m blocks). This is the geometry the character actually touches.

The character's traces, node standoff, and body clearance all interact with the collision surface. So a lattice built from the render skirts floats above the real face by the block-max-vs-per-cell height difference (up to ~0.6 m here), and the nodes fail the surface audit because they were derived from the wrong surface.

▸ FIX

Build the character-facing algorithm entirely from a block-max reduction that is byte-identical to how the collision surface is generated — never from a single render cell's height.

  1. Reduce the height grid to the collision blocks: blockMax(bx, by) = max cell-step over the 4×4 cell block. Match whatever reduction your collision builder uses, exactly.
  2. Sample that field for every grip/node position, not renderCellStep(x, y).
  3. Make the audit re-walk the block-max field analytically (no raycasts). An analytic re-derivation is both cheap and independent proof that the lattice is collision-derived rather than render-derived.

Verified live (fixed seed, engine 26.07.08e): 30 auto-placed planar climb-node fields produced an analytic audit of worstPerp = 3.0 cm (exactly the configured node-proud offset — grips sit dead on the collision plane) and worstBand = 0.0 cm (every node inside the re-derived block-max vertical band) → 0 violations.

Companion rule (mantle / cap overhang): only accept a wall whose block one step behind the lip is walkable and not higher than the top. This rejects any auto-placed lattice whose top node would sit under an overhanging lip.

▸ WHY IT WORKS

Character interaction is defined against the collision surface, so any geometry the character grips or stands on has to be derived from that same surface to line up. Sampling the render mesh derives geometry from a surface the character never touches, which is why it floats. Reducing with the identical block-max rule the collision builder uses guarantees the derived points land on the exact plane the physics uses; auditing analytically against that same reduction proves it, without depending on raycasts that could mask the error.

Verified on engine 26.07.15a: 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