Terrain, and a hard look under the hood The feature the level layout has been missing is here, and alongside it went a lot of work on editor speed and on getting the lighting right.
Terrain
Terrain is a new object type, not a separate editing mode. A heightfield is built as a closed solid, with a surface, a skirt and a bottom cap, so it is an ordinary participant in the boolean: a Negative box carves a cave or a building foundation straight into the ground, a Positive wall unions onto it. Nothing downstream needed a special case.
The Field settings run from 32x32 to 192x192 cells, with Size, Height and Base, the last of which decides how deep the solid runs below the lowest point. The Height brush works in Raise, Lower, Smooth and Flatten modes; Flatten levels to the height you click first, so a building pad takes one stroke. Ground Layers gives you four layers. Layer 0 is the object's own Material texture, so an unpainted terrain looks exactly like any other object, while layers 1 to 3 are painted in with their own tiling. Paint resolution is independent of the mesh: the weights live in a texture rather than in vertices, so you get crisp material edges on a low-poly heightfield. The choices run from 256 up to 2048, saved as a PNG next to the map. Exports carry the result. On OBJ, glTF/GLB and the Unity bundle, each terrain's splat is composited into a single albedo texture, so engines with no terrain shader of their own still show what you painted. Lightmaps bake over terrain like any other geometry. Two light leaks, both fixed If a point light sat exactly above a receiver, or lined up with an axis-aligned wall, a bright line ran across the shadow. One divide was to blame: a ray direction component of exactly zero makes its reciprocal infinite, and multiplying that by the difference of bound and origin gives NaN. GLSL leaves min and max undefined for NaN, so the BVH node quietly failed its test, the occluder inside it was never traced, and those texels stayed lit. The reciprocal is now clamped to a large finite value: the search slab on that axis simply becomes very wide, which for a ray that does not move along the axis is also the correct answer. The second one was thinner and meaner. Two triangles sharing an edge do not evaluate that edge from the same numbers, so a ray aimed exactly at it could be rejected by both and pass through a surface with no hole in it, and a light straight over the diagonal of a quad did it every time. The edge tests now carry a small tolerance: erring towards a hit costs a millionth of a triangle, while the alternative was an unshadowed line across the floor. Both now have headless regression tests, so neither can come back unnoticed.
Speed
Bake preparation is multithreaded. Texel rasterization and page assembly run per atlas page in parallel; on a level with hundreds of pages this was the bulk of the wait before a single ray was traced. Cancel also does what it says now, where before it did nothing until the whole preparation stage was over. Walk mode and face hover trace an acceleration structure instead of scanning every triangle in the level, and picking rejects whole objects by their world bounding box first. Selection outlines cache their edge table; it used to be rebuilt from scratch, every frame, for every selected object, even though the topology it comes from does not change per frame. Vertex buffers upload only when the mesh actually changed, which on a big level saves pushing around 19 MB per frame of data that had not moved. Terrain painting streams through a per-frame staging buffer. Every brush dab used to be its own submit with a full pipeline drain, and that stall was the hitch you could feel mid-stroke.
Fixes Present synchronisation now uses one semaphore per swapchain image rather than one per frame in flight. The CSG cache is keyed by object id instead of by pointer: a deleted object's address could be reused by a different one, which in the wrong circumstances rendered somebody else's baked geometry. Gizmo handles scale correctly in orthographic views and follow the avatar's field of view in walk mode. A reopened map redraws its terrain splatted in the baked view and keeps its own painting instead of, in one unlucky case, the previous map's. Terrain layer textures now refresh when a project is opened: every other object resolves its texture by name each frame, but a terrain's splat descriptor caches the image, and a document opened mid-frame could leave it holding the fallback checker for good.
As always, a bug report with a .lmmap attached is worth ten without one.
