Skip to content

22 April 2025 - The New Terrain System

<<< Previous Post Next Post >>>


  It's been a while!

  In the last blog post, I went over a few things about the game itself, but one of the topics also covered was about the new terrain system. I want to talk some more about that and what has been going on since that time.

The Primary Redesign

  The most noteworthy change made to the terrain system is the removal of node-based world objects. This kind of goes against the grain of typical Godot design, but it was deemed absolutely necessary for proper enforcement of design rules. It also opens the door to some improved performance opportunities (Like. By a lot. (like, holy shit tier of performance changes))

Performance improvements from abandoning nodes

  1. No nodes means no unexpected external access.
    1. Terrain geometry and physics objects are both opaque to outside code; a modder's code shouldn't just come in and delete any of the objects associated with terrain. Previously, this was possible to do (possibly on accident) which would crash the game as the terrain system detected a fault. Now, it's still possible, but significantly harder.
  2. No nodes means no scene tree access.
    1. The world benefits from parallel design, that is, the ability to task multiple threads with world generation to work together all at once.
    2. Adding nodes to the scene tree requires calling back to the main thread. This is, in general across all computing, an extremely expensive1 thing to do that is best avoided if at all possible.
  3. No nodes means no resource allocation.
    1. Resources have their references tracked, and also a bunch of code to make sure they are used properly. They must also be allocated as distinct objects, wrapping around the low level ones.
    2. This design skips the nodes and resources, and directly goes to that low level data manually.

  These two differences alone have improved terrain generation performance by almost 1500%! In fact, running this new system took 2.5 seconds to generate a 12x12x12 of chunks on a single thread. The old system took around 20 seconds to do that on 20 threads. Again, most of the time being spent here was calling back to the main thread. So if you ever needed a reason to know why you should avoid jumping across threads, that's why. Does this justify me saying "like, holy shit tier" at the start of the post? Yeah it does.

What else should get the no-node treatment?

  Doing this to the terrain system has been hugely beneficial to code cleanliness and performance alike. Unfortunately this has kind of opened the Pandora's Box of performance optimizations for release. Namely, there's two other locations of the code that might seriously benefit from this type of change.

  1. The world itself. This may be a bit confusing - only the terrain (like, the physical object and the mesh) dropped nodes. The actual world itself is still a special Node-extending type called ConservatoryWorldNode. I access its World3D to get ahold of the physics space (the low level container that stores physics objects) and render scenario (the low level container that stores things that can be rendered).
  2. Entities. Entities use CharacterBody3D right now. This is great 99% of the time, but some entities have special needs such as being disallowed from moving, which I cannot enforce directly2. Additionally, this means I have to track child nodes and it becomes a huge mess especially if some modder deletes a required object without realizing.

What took this change so long?

  Burnout. That's it. That's the paragraph.

Conflict: This makes things harder for modders

  One of the side effects of this change is that the convenience methods offered by Godot largely become unavailable. While terrain is functionally identical to many StaticBody3Ds, raycasting or shapecasting to it won't reveal a CollisionObject3D to you. Instead, you'll have to use that obscure secondary parameter of the collider's Rid to access the information. I anticipate this will be quite confusing to newer modders. It is extremely rare for Godot code to do this as far as I know, and so someone somewhere is bound to be confused as hell when they see their raycast hit something but the object it hit is null.


  1. In computing, "expensive" means "has a high performance cost and takes a long time to do". In the specific case of the world generator, this requirement to call back to the main thread took so long for the computer to do that it actually caused lagspikes when adding chunks to the world! 

  2. "Enforce directly" meaning "outright prevent it from happening". The only solution I have now is reactive changes i.e. setting the position value when I detect the Changed signal.