Skip to content

19 May 2025 - The "De-Noding", Blog Changes, and Engine Upgrades

<<< Previous Post

You're on the latest post!


  Hello again! This is another big post. No intro - we've got things to talk about!

Blog Changes

Visuals and Fixes

  I made a number of changes to how the blog handles its visuals, and fixed some issues.

  1. Most obviously, the blog is now w i d e. I do this for the internal documentation page, I think it looks a bit better, though it does make text look somewhat more dense.
  2. Search is now fixed. Apparently the script I used to add the blurred backdrop broke the search system. I'm not sure how.
  3. There is more contrast on the list of posts, and the section dropdown.
    1. This was needed because with the fix to the search system, I'm no longer able to have a gradient on the screen itself (limitation of MkDocs, I know how to do it in HTML obviously). The lack of a blur reduced contrast due to the sharpness of the background without it, so this compensates.

New Post Format

  Two posts ago, I made a channel in the Discord server, #development-ramblings. This channel has served the purpose of being a dumping grounds for the small bits and pieces of work that I do over time, the ones that themselves aren't significant enough to make a blog post of their own.

  Prior to this channel's creation, I just had to keep mental footnotes on all of my progress on the game, and frankly it's hard to remember what all has been done in a game's alpha phase because so much is going on.

  As a result of this:

  1. Blog posts are now going to be recaps of what I have posted in #development-ramblings*
        * For the most part. Some stuff I do purposely save for these posts.
  2. Blog posts are mostly directed at people not in the Discord, or for people who don't want to dig through all the "raw" posts that make up #development-ramblings's content.
  3. Blog posts may feel like they carry more information for this reason, depending on what has happened in the time since its previous post.

The "De-Noding"

  In the last couple posts I have gone over topics related to game system changes in lieu of The Conservatory's custom fork of the Godot engine, primarily with respect to my abandonment of the Node system. You don't need to read the previous posts. I'll recap here.

  Godot's unique design actually intends for this - nodes are really just wrappers around low level objects that the engine works with directly. They make some (generally correct) assumptions about how you want to use them, and implement most of the tedium for you, greatly simplifying code.

  For those of you who know more about Godot, you may be inevitably wondering why I chose to do this, especially given the added complexity of making the choice I did. There were a few reasons, but among the largest:

  1. Scene tree access requires being on the main thread, or deferring calls. This is by far the biggest reason for the change.
    1. The important detail is that syncing to the main thread is slow, especially in Godot.
    2. As a reminder, there was a 1500% performance boost in the terrain system when it was changed to not use the scene tree.
  2. Access to nodes allows unintended behavior. This is more of a personal reason, but one of the things I needed most was "idiot-proofing" on my data. Under the old system, it was possible for people to edit the data of something that the game expects to be made a certain specific way.
    1. If the data was accessible, novice modders may cause a problem without understanding why it happens, and possibly try to fix it with some spaghetti that makes the experience worse for everyone.
    2. In particular, modders unexpectedly reparenting or freeing nodes used to have to cause a crash since there was no realistic way to handle this. This required a lot of spaghetti code and sealing various virtual methods of Godot's node types.
    3. By requiring access to the low level data, it effectively adds a small skill floor, and draws attention to the systems that use that data, making it very obvious how it is intended to be used so that the modder can mimic this where needed.

  I need to address this: Working with low level objects is hard. As I mentioned in the last post, I intend to alleviate this problem by allowing nodes to be used anyway for things like prefabs and character models.

  Finally, in the last post, I mentioned that raycasts would not provide a CollisionObject3D to you. This is still true, however it has been improved. Why settle for a CollisionObject3D when you can just have the entire AbstractEntity? As it turns out, Godot doesn't actually care what an object ID is, so setting custom IDs on objects - an operation that would typically be used to bind an RID back to its Node (and by extension, is how raycasting is able to provide you with a Godot object in the first place) - allows me to assign my own game objects to raycasting targets. Relevant extension methods, like RayCastResult.GetHitEntity(), exist. More info on RayCastResult below.

Engine Fork

  Not like the food utensil; this is a code fork - a derivation of the original code edited to suit my needs.

  That's right, I have failed the godot game dev try not to fork the engine challenge (impossible difficulty). However, these changes are nothing short of a requirement for the design of my game or for having good performance. Most of these changes are hacks that existed in C# (and were like, really bad for code stink) that I eventually ported into the engine.

The most notable features:

  • Simulation3D is a new, advanced node type designed explicitly for The Fabric of Reality (the game's main class). The game client will directly render it exactly. The game server will use them to simulate several worlds in parallel.
    • Unlike SubViewport, this is not rendered to a target GUI element or texture.
    • This class is also heavily managed by internal code; unexpectedly deleting the node or its World3D, or attempting to remove it before the WorldInstance (A pure C# game object) is unloaded will crash the game, enforcing the extremely strict use guidelines it has.
  • Early implementation of apples's fork that adds support for the Stencil Buffer.
  • Fix to Material.GetRid() not being overridable.
  • CharFXTransform (used in RichTextLabel's custom effects) now has context to the current RichTextLabel it is rendering for.
  • RayCast3DDirect is a type which supersedes RayCast3D. Unlike its sibling, the direct variation must be casted manually (it will not do this on physics steps for you). It also provides a method to cast statically.
    • RayCastResult is a new type which returns the result of a raycast. It is intended for use with RayCast3DDirect.CastStatically(...) as a significantly improved replacement to the Dictionary returned by PhysicsDirectSpaceState3D.IntersectRay(...).

  In particular, Simulation3D is somewhat of a personal marvel. I had qualms with its predecessor, a plain Node-extending C# type called ConservatoryWorldNode. It was way too easy to irreversibly screw up the game if you tampered with this node. Moreover, this node did not support multiplayer because it used the root viewport for its world. Simulation3D fixes both of those problems.

  Simulation3D was designed for the single purpose of not letting that happen (easily). In fact, it's so tightly managed that you can't even create it. It's a hilariously stupid trick, but it is a Godot abstract class; you can't Duplicate() it, serialize it, or instantiate it. FabricOfReality is given sole management permissions to it via the use of low level pointer logic. You literally have to talk to the engine using raw memory in order to even hope to begin to manage the type.

  Thankfully, Simulation3D's public interface (the stuff you use as a modder) is fully capable and just as easy to use as the rest of the game. It is your interface to the Godot engine through the game's world type, on my terms.

Closing Thoughts

  All around, a significant amount of progress has been made these past couple weeks. The entity de-noding is basically done, I just have to reimplement physics in lieu of the new Simulation3D node.

  I still have some more work to do on Simulation3D. In particular these things bother me:

  1. A Simulation3D is intended to relate to a world, not a viewport.
    • Its use of Viewport as a base type is a compatibility choice: in the scope of "give me the place that this thing is rendering/physically simulated" (aka calling node.GetViewport().FindWorld3D()), Simulation3D does need to behave as a Viewport instance, so that way Node.GetViewport() returns it.
  2. All of the methods to set up render properties need to be in sync.
    • The intent is that if you set the properties of a Simulation3D that pertain to rendering in the game itself, i.e. AA type, Vsync, so forth, it should affect the game in its entirety. Again, while it inherits from Viewport, it shouldn't be treated like one conceptually.
      • In a more perfect case, these methods would be hidden or marked as deprecated to discourage their use, as it may be confusing that editing one Simulation3D's properties affects all of them. I doubt I can do that though.
    • On the game client, Simulation3D exists in a strange "half-transparent" state. Since the client can only see one world at a time, even if multiple Simulation3Ds are loaded simultaneously, it sets the game's root Viewport to its own world privately (as in, find_world_3d() has been modified to return it).
  3. I still don't have it override anything related to 2D worlds.