Omnith: a C# simulation engine running 10,000+ autonomous agents at 60FPS, built for infinite moddability
Going over my biggest C# project. Simulation Engine for a God Game I hope to release on Steam.
Omnith began as a Godot demo for GOAP (Goal Oriented Action Planning)
I made this demo originally mostly to show off. GOAP is a pretty complex algorithm to get right and I was very proud to have a project with an optimized version of it. The Godot demo had a few AI-generated sprites that would be driven by a very basic list of needs: Warmth, Hunger, and Sleep. GOAP’s job was to find the cheapest path to solving those needs. For example it would intuit that in order to become warm it needed a campfire -> to get a campfire you need wood -> to get wood you need to chop down a tree -> to chop down a tree you need to be near a tree.
I found this really charming because it reminded me a lot of Rimworld, a video game that I have maybe 200-300 hours on over the last 5 years. It’s a colony management game that I think is one of the best in the genre. I’ve poked around with recreating Rimworld before, but it was always a weekend practice thing rather than an actual project. I even made a few mods for it not too long ago, testing out things like adding an AI chat into the game to help you manage your colony. (ColonyCopilot)
I finished that demo around November 2025 and decided I wasn’t going to mess with it much more after that. But in March of 2026 I went down a rabbit hole of imagining how cool more games could be with Rimworld’s XML-based modding technique. I had messed with Rimworld’s XML modding a bit but always thought it was verbose. I thought JSON would make things easier to read and might help with the types of data that could be represented. After remembering the Godot demo, I started building Omnith around it. It was built on the idea of a:
- High performance
- Infinitely moddable
- Data driven
- Simulation
- Sandbox experience
Now 4 months of on and off development later:
Omnith is an optimized simulation engine built in Godot and written in C#. In the process of being turned into a God Game.
It uses an Archetype ECS (Entity Component System) and bypasses Godot’s node tree by sending requests directly to Godot’s rendering server. It’s akin to Unity’s DOTS or some more hands-on game engines like Bevy.
It is extremely data driven, using “spec” data (short for specifications) stored in files to derive visuals, logic, and all other behavior of individual entities and the objects/systems they work with. This is directly inspired by Rimworld’s “Def” architecture for data. In fact, much like Rimworld, all of the base game’s assets and data are stored as a mod instead of hard coded into the game. For anything non-code, I write systems in the exact way that a modder would.
It extends even further than just the spec files. Every part of the game is designed to use reflection to automatically detect modded code and use it. Systems, services, and components that were made by modders get first-class access to the game’s flow. Of course that’s where I still have a lot more control than the average modder since I handle the shape of the architecture that is being used, but I still write code in a way that is idiomatic to how modders will be expected to write their modded systems. The goal is for mods to never need code patching for adding new features. The only time they would ever need patching is to change the code of a system I wrote directly into the game.
Tech Stack
Beyond bypassing Godot’s node tree, Omnith barely uses Godot. It has a single node called “OmnithGodotBridge” to initialize itself, alongside a handful of nodes for wiring back into things like rendering, audio, and UI. Everything that happens in the simulation is pure C# with no pre-existing ECS libraries. The only exception to the use of C# is a handful of hot paths that I profiled via JetBrains Rider’s “Timeline Profiling” and decided to transition to Rust. I use FFI to bridge between C# and Rust. The actual Rust implementations were mostly handled by AI coding agents.
The algorithms that got rust-ified:
- Painters-algorithm sprite sorting
- Flood fill algorithms
- A* pathfinding
- 100,000+ shadow visual instance management
God Powers
[Below is a player using their god powers to strike down a mortal]
Scale
Because of my relentless obsession with profiling, I have managed to scale up Omnith to run 10,000 GOAP-powered agents (entities with agency, not AI/LLM) moving around and changing the environment of a 1000x1000 tile world. Each little person has a complex inner life with emotions, memories, and relationships. All of this runs at 60FPS on my 2025 HP laptop with a Ryzen AI 5 and 16GB memory. It runs at an even higher scale on my 96GB workstation, pushing 50,000 agents with only a few frame spikes.
When I originally made my GOAP demo and posted numbers for it, I thought they were impressive. The number of agents it was capable of was around 800-1500 at 60FPS. But I’ve easily 10x’d the performance of that system through better algorithms, deferring to Rust for hot paths, and doing a proper Archetype ECS for taking full advantage of cache lines.
Why Godot?
That’s the obvious question after I tell you that I go to great lengths to avoid using Godot’s pre-built solutions and node tree. The answer is both really simple and sorta complicated.
The simple answer: I just like godot. I don’t use the UI, but I do like being able to just run C# code somewhere with proper 2D rendering.
The more complex answer: I think that Godot being written in C++ and handling file stuff for me is genuinely a differentiator. I don’t want to write my own file management, and I don’t want to rely on libraries that are maybe meant for C# web backends rather than video games.
I like working in established frameworks where AI coding agents have ample training data + plenty of docs and forums to look up when things go wrong. Scenes are plaintext and easy to read, and everything gets tracked by default in git, so agents can manage commits right away. Coding agents and Godot work surprisingly well together out of the box.
Bevy would be great if I were letting an AI coding agent handle every line of code, since Rust is genuinely much harder to write for systems like this. The ECS part would fit cleanly, but I also use Reflection heavily to handle automatic registration for systems. (This is part of that “mods don’t need code-patching” flow.) I know there’s bevy_reflect but I’d much rather work in C# for reflection-heavy projects and defer to Rust only when needed. Dependency Injection is also just easier in C# in my opinion, and this project is built on dependency injection probably more than anything else I’ve ever made.
Unity was actually my first choice! But to this day, they still simply refuse to make proper scaling controls for their UI on Linux. Even if that wasn’t the case, Unity is so much heavier and slower, which is all downside when I still wouldn’t use any of its built-in shortcuts. I would likely treat Unity the exact same way I currently treat Godot, except I’d have to fight even more architecture that was decided for me.
Testing
This is the part that I think is the weirdest part of Omnith. And I think that a lot of game developers overlook it. The second I was convinced that this was going to be a published product one day, I started writing unit tests. I used AI coding agents to multiply that exponentially. The codebase as of writing has 2,887 xUnit tests. The ratio of test code to production code is 0.81:1
For every 1,000 lines of code I write, an AI agent writes 800 lines of tests. I’m well aware that AI models like to write useless tests, so I also go back through and use Stryker.NET for mutation testing. My agents do most of the time write high quality tests, but they can get lazy and mutation testing is the easiest way to flag and fix lazy tests.
And that’s only half the story. I eventually realized that while unit tests are great for algorithms, they don’t do you any good in verifying the stability of simulation logic. Thus I built what I call the “Trial” framework. Trials are a DSL for defining scenarios and their valid ending states. I’ve written about 80 of them. The trial runner spins up Godot in parallel on my workstation to get 32 simultaneous simulations of actual game logic running in it’s own thread. Each trial run finishes in about 2 seconds, so it ends up taking 2-3 seconds to run the 80 Trials. Each one returns the same way a unit test would, with validation that the expected result is still happening. I guess you could call it integration testing, but I thought Trials sounded cooler.
Trials enable test driven development on entire features by agents, which has indeed multiplied the amount of features I can defer to AI agents while still being confident in their quality because the behavior gets verified that every single system in the game is still working as expected. Every Trial is seeded for determinism and has tolerances in the end state so I can define ranges of valid results rather than just boolean validation.
AI usage
I’ve talked before in other blogs about how I use AI in my projects. It’s a force multiplier, but especially for something as fragile as a simulation engine, It’s sort of like a glass cannon. It would be very easy to define a dozen different features and not think too deeply about how they fit together, but the way that these systems fit is genuinely not something AI is capable of keeping in it’s head. I often will spend an hour or two just brainstorming the exact shape of a system before I ask the AI to implement it.
Unlike a few of my rust projects, I have written a decent chunk of Omnith’s code. I am fluent in C# & OOP patterns from my years of working in the Unity engine and C# web backends. But I still take advantage of frontier models because I genuinely do believe they are the most efficient way to get any software work done now. I’m sure public sentiment will shift more that direction but I think the politics of it all are a bit hectic right now. I just like being honest about where the distribution of effort is, It’s 90% architecture/system design and 10% finding niche bugs when Frontier models can’t find them.
Source?
Omnith is not open source, probably the only project on my portfolio that will be that way. And while I would love to show you more, maybe outline exactly how it works and why it’s so cool, I still have dreams of someday publishing Omnith as a genuine game on Steam. It’s very far away from that dream now, but I think I will keep this close to my chest beyond some fun write-ups and maybe dev logs as the project takes a more visually appealing shape with a proper gameplay loop.
I genuinely might write more about Omnith, it’s becoming a big passion project, and I’m not sure when I will actually have a public version out to try, but it might be coming sooner than I thought. Thanks for reading!