/ 00
article

Six years of an engine.

MECS is six years old. The thinking that produced it is twenty-three. This is the origin story — from peer-to-peer lockstep RTS networking in 2003, through the Dropzone ECS engine in the mid-2010s, into the educational-MMO project that made the case for a new engine, to the thesis I wrote down in late 2020 that became MECS.

MECS — the Modular Entity Component System I have been building since 2021 — is the engine underneath every game project I have shipped or attempted in the last five years. It is also the substrate underneath the AI orchestration work I have been writing about elsewhere. This post is the first in a short series on it. The other two will cover the platform that grew up around the engine in 2024 and the AI-era developments — source generators, compile-time diagnostics, the NuGet breakout — that landed earlier this year.

But the engine is six years old as a codebase, give or take. The thinking that produced it is twenty-three.

The honest version of the MECS origin story does not start when the first commit was pushed. It starts in 2003, in a building outside Baltimore, with me trying to figure out how to keep a 28.8-modem-connected eight-player real-time strategy game in sync across machines that had wildly different graphics cards and processors. It runs through nearly two decades of multiplayer game work — most of it at studios that share a lot of personnel and a strong engineering tradition — before it arrives at MECS as code. Without that pre-history, the design choices in MECS look arbitrary. With it, they look almost inevitable.

So let me start where the through-line actually begins.

Building a multiplayer RTS in 2003

I joined Big Huge Games when I was twenty-four. The company was Brian Reynolds’s studio at the time, and the project was Rise of Nations. I was, for the duration of the original game’s development, the multiplayer programmer — the only multiplayer programmer. RTS games were selling in big numbers in those years. The engineering substrate for building them was, in retrospect, almost charmingly thin.

There were no off-the-shelf engines you could license and build an RTS on top of. Every studio wrote their own. There were no networking libraries — not in any meaningful sense; the Berkeley sockets API and WinSock were what you had, and “high level” usually meant somebody else’s wrapper around select(). NATs were a new enough concept that connection establishment between two players behind home routers was actually a research problem. People still played on 28.8 and 56k modems. GPUs were just starting to be a thing the average consumer would buy. The Pentium III was the dominant CPU. Cloud compute did not exist as a category; “deploying a server” meant putting a physical machine in a colocation cage.

The only published references I can remember consulting back then were Tim Sweeney’s writings about the early Unreal engine — at that point still the codebase behind the original Unreal, not yet the platform it would become — and Mark Terrano and Paul Bettner’s GDC 2001 paper 1500 Archers on a 28.8: Network Programming in Age of Empires and Beyond. We knew the team at Ensemble; the RTS world in those years was a small enough subculture that the studios building games in the genre talked to one another.

The networking architecture that we and Ensemble both used was peer-to-peer lockstep simulation. The idea is straightforward in description and unforgiving in implementation. Every player runs the full simulation locally. Each player broadcasts their input — their moves, in the parlance of the time — at a fixed turn cadence. I remember ours running at roughly six turns per second. At the end of each turn, every machine applies every player’s collected input to its local simulation state, advances by one tick, and emits a checksum of the resulting state along with the next turn’s moves. If any client’s checksum ever fails to match any other client’s checksum, the simulations have diverged. The game is out of sync. Anyone who played multiplayer RTS in the early 2000s remembers those words. They ruined a lot of weekends.

Lockstep imposed a discipline that has stayed with me. Every interaction that touched simulation state had to be deterministic. Every random number had to come from a seeded generator the entire connected set of clients agreed on. Every floating-point operation had to be carefully scrutinized because IEEE 754 is reproducible within an architecture but the order of operations matters, and a different order on one machine produces a different result. The simulation could not be allowed to read the wall clock. It could not be allowed to read any input the network had not delivered yet. The simulation had to be a pure function of its prior state and the input sequence. If any line of code violated that purity, the game went out of sync, and we found out about it in the worst possible way — at a milestone playtest, in front of the producer, with a checksum log that pointed at five hundred possible culprits.

Two further insights shaped the architecture for me, both during Rise of Nations.

Not all state is game state. People in 2003 were buying actual graphics cards — meaningful money, for the time — and they wanted to see the results on screen. A high-end machine could render at sixty frames per second; a min-spec machine struggled at fifteen. But the simulation had to advance at the same rate on every machine, because lockstep. That meant the render frame rate and the simulation tick rate could not be the same number. The simulation needed to advance at its fixed deterministic cadence; the render layer needed to interpolate between simulation samples and update at whatever pace the local machine could sustain.

The implication of that, once you start designing against it, is that you cannot have one state — you need at least two: simulation state and render state. And the moment you have two states, you have a problem. In a large codebase, with twelve programmers, those two states will be mixed within hours of the rule being announced. Somebody will store a particle position in the simulation state because it was convenient. Somebody will compute a damage roll from a render-frame-rate timer because they did not know better. The simulations will diverge, and you will spend two weeks finding the offending line.

The way we solved this on Rise of Nations was to make the C++ compiler enforce the firewall. Every game object was actually three class instances. One held simulation state. One held render state. The third was a bridge — the only object that could write to either of the first two, and only through an input-command queue. The bridge accepted commands that would be processed at a later tick to advance both state sets. There was no other entry point. If a programmer tried to mutate simulation state directly from render code, the compiler refused. The three-class pattern (we called it the tri-class) did not solve the entire problem — bugs still crept in — but it solved enough of it that we could ship the game.

That architectural choice — a hard, compiler-enforced separation between simulation state and render state, with all mutation flowing through a queued command system — has been the foundation of every multiplayer game I have worked on since. The exact mechanism has evolved many times. Peer-to-peer lockstep gave way to authoritative-server architectures once bandwidth and cloud compute made server-side simulation viable. The tri-class became, in later projects, a two-class split with the bridge implemented as a separate subsystem. The C++ inheritance hierarchies became component arrays. But the principle has not changed. The simulation and the view never share mutable references. Every change is a command issued to a queue and processed at a known later moment.

Cache coherence is everything once you have hundreds of units. Rise of Nations had hundreds of animated units on the screen at once, plus arrows, plus particles, plus weather, plus a world map. We needed it to run on min-spec hardware. A cache miss on a Pentium III in 2003 was roughly a thousand times more expensive than the average instruction operating on data already in cache. A thousand times. If you missed the cache once per unit per update, you did not have a real-time game. You had a slideshow.

The optimization that mattered most was structural. The naive approach is array-of-structs: an array of Unit objects, each one carrying every field a unit might need — position, health, animation state, AI state, rendering state, owning player, current command. When the update loop walks the units to do, say, a position-update pass, it loads the entire Unit struct into cache for each unit, uses three of the fifty fields, and evicts the rest. The cache hit rate is terrible.

The fix is to invert the layout. Instead of an array of Unit, you have one array per field. An array of positions. An array of healths. An array of animation states. A given unit’s data is no longer contiguous in memory — its position is in one array, its health is in another — but the position update loop now walks one tight contiguous array of position data, fits dozens of units in cache at a time, and runs in a fraction of the time. When the render loop wants animation state, it reads only the animation-state array. Each phase of the update touches only the data it needs.

This pattern has a name now. People call it data-oriented design, or sometimes Entity Component System architecture, with components stored as columns in a table rather than rows. In 2003, I am not certain we had a name for it. We were not consciously building an ECS; we did not yet think of ourselves as building one. We were trying to keep the game playable on a Pentium III with sixty units fighting on screen. The struct-of-arrays optimization was a means to that end. It was, in retrospect, the primordial form of what would become ECS as the term was later codified. We were doing it because the cache architecture of the early 2000s would not let us do anything else.

So by the time Rise of Nations shipped, I had absorbed two ideas that would shape every engine I worked on after: a hard simulation/view separation enforced at the language level, and a data layout that organized state by use-pattern rather than by entity identity. I did not know they would still be the foundational decisions of an engine I was building twenty-plus years later. I just knew they worked.

Sparkypants and the Dropzone engine

In 2011 I helped start Sparkypants Studios with Jason Coleman, one of the original founders of Big Huge Games. There was substantial personnel overlap between the two studios. The engineering culture was continuous — the same emphasis on craftsmanship, the same comfort with low-level systems work, the same instinct that hard things should be built well rather than glued together. That shared culture was what made the next chapter of engine work possible.

Around 2014, a number of us at Sparkypants set out to build a new engine for the game that would become Dropzone. The ECS pattern was crystallizing in the industry by that point. Adam Martin’s earlier blog posts had pushed it into the conversation. Game-engine architects were talking about it at GDC. Several teams were converging on data-oriented entity systems independently of one another. We committed to that shape.

I am going to be deliberately non-specific about the architectural details of that engine in this post. It is not mine to describe in public, and the details are not what mattered for the eventual arc into MECS. What mattered, for the purposes of this story, was the experience of building games on top of it. The engine was, in honest engineering terms, extremely efficient at runtime — the kind of efficiency that changes the operational math of running multiplayer game servers at scale. It was also unforgiving to iterate in. The same characteristics that made the engine fast meant that implementing gameplay logic required carrying a much larger mental model than most programmers were used to. The engineering and gameplay teams Sparkypants had were genuinely world-class, and that fact is the only reason Dropzone shipped weekly live-ops updates for months after its Steam early access launch in February 2017 without major regressions. A less experienced team would have been overwhelmed.

By the time Dropzone wound down and I started looking ahead, I was left with two impressions of the engine in roughly equal force. The first was awe — the runtime characteristics were everything I had hoped for and more. The second was reluctance. I did not want to build my next game on that codebase. Not because it was wrong, but because it asked a lot of the people building games on it, and I could see myself burning out trying to find the fun in new mechanics while simultaneously fighting the implementation friction of a deeply technical engine. The engine was a marvel. It was also a forcing function for an engineering-first culture, and I was no longer sure that was the right tradeoff for my own next thing.

A studio, and the education-MMO project

I left Sparkypants in 2018 and incorporated Wood Fired Games LLC shortly after, originally as the contracting vehicle for a Design Director engagement at Bad Robot Games. Wood Fired received its first check made out to the company that December — a contractor payment from an educational gaming company I had started doing some work for in parallel. The studio existed as a legal entity from that point on. It would not become operationally active for another two and a half years.

In 2019 I joined the educational gaming company full-time as EVP of internal game development. Wood Fired Games went dormant. The new company operated a platform that delivered short-form educational games to students through schools, run by one division. My division ran the team responsible for the marquee long-form game experiences the platform’s audience played regularly.

In late 2019 the team I led decided to rework the core student experience into a shared multiplayer world — effectively an MMO for an educational audience. The premise was good. Kids working through educational content in a shared persistent space, encountering each other, building together, would produce engagement profiles the existing short-session model could not. The team was capable. The pieces were there.

The execution ran into a problem I had not seen at the studios I had come from. The platform side of the company — the systems delivering the educational sessions, the user identity infrastructure, the analytics — was built by engineers from a SaaS background. Modern web stacks, request/response patterns, database-backed session state, the whole vocabulary of web infrastructure. My team came out of the games industry — engines, simulations, frame-rate-sensitive update loops, networked authoritative state. Both teams were strong. The cultures and the methodologies were different in ways the org charts did not name.

When we tried to combine them into a shared multiplayer world — the kind that required SaaS-shaped identity and analytics on top of game-engine-shaped real-time simulation — the gaps between the teams’ default assumptions manifested as gaps in what was technically deliverable to the students. We did our best. The students still got an experience they enjoyed. But the experience we delivered was constrained, in real and visible ways, by what the two teams together could build with the technology each of them had natively.

That bothered me, more than I expected it would. By late 2020 I had decided to move on from the full-time role, and I could not stop thinking about what the same project would have looked like running on top of the kind of engine we had built at Sparkypants for Dropzone — but with the iteration friction of that engine somehow solved. The infrastructure cost would have been lower by an order of magnitude. The simulation throughput would have been higher. The platform-versus-game cultural divide would have been narrower because the game engine and the platform services would have been one codebase. The constraints that limited what we delivered would have been different constraints, and most of them would have been smaller.

I did not, at the time, write any code in response to that line of thought. I just kept thinking about it.

The thesis, late November 2020

By Thanksgiving 2020 I had turned my attention to what Wood Fired Games would build next. The studio had been a dormant LLC for two years at that point — incorporated, capable of receiving income, but operationally idle while I was working full-time elsewhere. With the decision to move on, the studio was about to wake up, and the question was what it should wake up to. The shape of the project came out of two specific frustrations I had been carrying:

The first was Dropzone’s iteration friction. I had seen what an ECS engine in C could do operationally. I had also seen what it cost to maintain development velocity on top of it. I wanted to find a way to keep the runtime characteristics — the per-host instance density, the cache-coherent update loop, the deterministic execution — while making it possible for a small team to iterate on gameplay mechanics in something close to the rhythm of a Unity project.

The second was the education project’s cross-team technical limit. I wanted the simulation and the platform services around it to be one codebase, written in one language, sharing one type system. If the team building the player-state service did not need to learn a different stack from the team building the game’s combat system, the things that limited what we shipped would be smaller things.

Out of those two frustrations came three goals I committed to in writing:

  1. Take the operational efficiency of a real ECS engine. Cache-coherent component storage. Authoritative-server multiplayer at high per-host instance density. Deterministic execution. The same shape Dropzone had.

  2. Multiplayer-first. Build the networking model into the architectural foundation. Not as a bolted-on layer. My background gave me an unfair advantage in the networking side of things, and I wanted the engine to lean into that rather than treat multiplayer as a feature flag.

  3. Iterate fast. Preserve development momentum. The most important ingredient in finding the fun in a game mechanic is the number of times you can iterate on it before you ship. If the engine costs you iterations, the games on top of it will not be as good as the engine’s runtime numbers say they should be.

Three goals will not get you to code on their own. I also needed to know which design ingredients would make them simultaneously achievable. My initial guesses, written down at the time, were these:

Modularity with carefully limited side effects. Code reuse across systems, but with state changes flowing through declared paths, not through arbitrary mutation. The lineage from the Rise of Nations tri-class architecture is direct. The command-queue pattern was the right answer in 2003, and I expected it to be the right answer now, generalized to a system layer.

No object-oriented programming. No inheritance hierarchies. No virtual dispatch. No this pointer. Components as data, systems as logic that operates over component queries, no classes that bundle both. The Dropzone engine had taught me that the OO ceremony was costing more than it was paying for, and the parts of it that did pay — encapsulation, polymorphism — could be replaced with explicit type systems and dispatch tables.

Auto-generated boilerplate. The single biggest friction on an ECS engine of the Dropzone shape was the wiring code. Every new component needed factory code, serialization code, network code, registration code — all of it formulaic, all of it manually maintained, all of it a tax on iteration. If I could generate that automatically from a high-level type definition, the iteration cost drops dramatically and the engine starts to feel as easy to work in as a Unity project, without losing any of the runtime characteristics.

That was the thesis. ECS efficiency, multiplayer-first, easy iteration. Modularity, no OOP, auto-generated boilerplate. I did not have code. I had a set of decisions I felt I could defend.

The first commits

I did not start writing code immediately after the thesis. The thesis was written in late November 2020. Through the winter and into 2021 I iterated locally on what would become the framework — sketching component models, trying out message-routing patterns, testing serialization approaches — without putting any of it into version control. I was operating Wood Fired Games on my own at that point, splitting time between picking up contractor work (including ongoing engagements with the same educational company I had recently left full-time) and the engine work I was beginning to take seriously. I had not yet built the discipline of frequent commits. The work lived on local disk and migrated between machines as I refined it.

The first commit that survives in git is dated April 17, 2021, on a repository I created for a roguelike side project I was using as the testbed. That commit landed as a substantial volume of accumulated work rather than a fresh start — months of local iteration becoming version-controlled in one push. The repo stayed under continuous iteration for the rest of 2021. By mid-May the simulation was already “reporting the full state to the view” — the sim/view separation principle from the Rise of Nations tri-class, instantiated for the first time in this codebase.

The structural milestone people tend to point at when they walk this repo’s history later is December 2, 2021. That commit message reads, in full:

“Built an ECS framework and started building out the game using it.”

It is tempting to call that the moment MECS was born. It is more accurate to say it is the moment the framework reached the shape I had been working toward all year. The five primitives I had been iterating on through the spring and summer (Sim, EntityLibrary, ComponentFactory, Component, Entity) landed in their named, recognizable form. Under various renames, those five primitives are still the core of MECS today.

Two weeks later, on December 17, the topological sort lands:

“…make the systems overlap properly and have a dependency hierarchy.”

Five days after that, on December 22, the same plumbing was extended to thread the component-delta record through the systems loop and make systems skippable when none of their input component types had changed during the frame. The code comment I left inline at the time states the invariant precisely:

“if nothing has changed then there is no reason to execute the system at all. Systems are ordered such that later systems always depend on components that are controlled by earlier systems. That’s required or this optimization won’t work.”

That paragraph encodes the contract between the topological sort from December 17 and the skip mechanism from December 22 — the optimization is correct only because the systems are already ordered such that producers run before consumers. That is the change-driven skip optimization I describe in detail later in this post, and it is, in retrospect, the architectural moment of the entire year. The same _executionOrder walk and shouldExecute skip is still load-bearing in 2026 — the line if (!shouldExecute && !executeAllSystems) continue; runs in every frame of every game I build on MECS today. The next day, on December 23, the unsafe Bitwise helpers showed up:

“…low level Bitwise unsafe code that should be fast.”

By early January 2022 there was a code-generation tool (“…a code generation tool to make it quick and easy to add new messages and components”) and end-to-end message routing was working (“Got the messages going all the way through the ECS setup and a response sent back to the bot console…”).

On January 11, 2022, I pulled the framework out of the roguelike project into two new repositories: WoodFiredFramework and WoodFiredEditor. The framework repo carried the simulation engine, the entity library, the message routing, and the component infrastructure. The editor repo carried the code-generation tool that turned database-defined type entries into runtime C# scaffolding. The day of the extraction, I renamed Sim to Simulation, because once the code was a library other code could consume, the name needed to be a noun rather than an abbreviation.

The early architecture was managed C#. Components were abstract managed classes. The entity library was a ConcurrentDictionary-backed store. The component pools were ObjectPool<T> over managed types. It was correct for a roguelike with hundreds of entities. It was not going to scale to the per-host instance density I had committed to in the thesis. The managed path had to go.

The name appears

December 13, 2022, a new project (project-space-rts) started, and the name MECS appears for the first time as a folder under WoodFired/. It was not a side project; it was the canvas for a wholesale rewrite. By Christmas the new structure was in place:

  • Components became unmanaged C# structs with explicit [FieldOffset] layouts, capped at 512 bytes, no logic — data only.
  • ComponentFactory<T> where T : unmanaged allocated MemoryOwner<T> blocks instead of pooling managed objects. Component IDs became ushort indices into those blocks, recycled on destruction.
  • ComponentHandle — a readonly struct pairing a ComponentType and a ComponentID — became the lightweight reference passed between systems.
  • EntityQuery got a proper API, returning iterable views over the entity table filtered by component-mask predicates.
  • Messages became ref struct Packet<T> where T : unmanaged, with a MessageHeader for wire framing.
  • UniqueID showed up as a 16-byte GUID wrapper, the primary key for every registry entry.
  • The BitArray* family — BitArray32, BitArray64, all the way up to BitArray4096 — landed as the backing for component-presence bitmasks and network fragment tracking.

The acronym is what the framework is. Modular Entity Component System. The modularity is the side-effect-limited composition that came out of the 2020 thesis. The entity-component-system is the formalized ECS shape that Dropzone had codified and Rise of Nations had foreshadowed. The naming was not the important part.

The important part was the performance unlock. The early framework had been managed C# — abstract component classes, dictionary-backed entity stores, object pools over managed types. That shape was correct for a roguelike with a few hundred entities, and it had been hitting a ceiling I could not push past while staying in the managed lane. The unmanaged rewrite was the experiment that found a way through. Once components became unmanaged structs in explicit memory layouts and Span<T> was used aggressively across the hot paths, the framework’s performance closed most of the gap with native C++. A side benefit was that the wire format and the in-memory format converged — component state could be serialized directly to the network without any managed boxing path — which made authoritative-server multiplayer feasible later. But the convergence was a consequence of the rewrite, not its motivation. The motivation was that I had found a way to make C# go fast enough.

Cross-project hardening

Through 2023 MECS lived as Unity source code embedded across a series of game prototypes. None of them shipped. All of them mattered. project-empire started in April. project-zombies in May. project-flame, project-vampire, and project-dwarves followed across the summer and into September. Each one stress-tested a different shape of game and surfaced a different class of bug in the framework. By the end of 2023, MECS had survived roughly ten Unity prototypes spanning strategy, simulation, action, and roguelike-adjacent genres. The framework’s surface area at that point was: a per-tick execution model with topologically-ordered systems, a message-routing system with Send and Loopback modes, a code-generator that ate database-defined types and emitted C# wiring, an unmanaged-struct component pool, an entity store with component-mask queries, and a delta-broadcast mechanism that would later become the basis of authoritative-server multiplayer.

The framework still had not been a production backend or run an authoritative-server multiplayer game in earnest. That changed in October 2023. But that is the next post.

What ended up mattering

The design decisions that survived all the way from the original 2020 thesis to the codebase as it stands today are these:

Sim/view separation, enforced at the language level. The simulation runs deterministically as a function of input. The view consumes deltas. They never share mutable references. The lineage from the Rise of Nations tri-class is direct — same principle, different mechanism. This single decision is why MECS games can ship with multiple clients (terminal, graphical, AI-driven) running against the same authoritative server.

Command-queued state changes. All mutation flows through messages that are dispatched to interpreters at a known later tick. Systems do not reach into the entity library and mutate components in place. This is the second decision that comes straight from 2003. It costs an indirection. It makes the entire state machine inspectable, replayable, and serializable.

Unmanaged components only, no logic. Components are pure data in explicit memory layouts. Behavior lives in systems. The cost is that components cannot carry strings, lists, or managed references. The payoff is performance approaching native — dense contiguous memory, zero allocation in the hot paths, and aggressive Span<T> use throughout. Wire-format alignment is a side benefit; the performance is the goal.

Database-driven type definitions. New components, systems, queries, and messages start as rows in an asset database, not as new C# files. The runtime knows about them because the code generator reads the database and emits the wiring. This decouples type definition from build cycles and answers the auto-generated-boilerplate part of the 2020 thesis.

Network-first messages. Every message is an unmanaged struct with explicit serialization, including messages that only flow inside the same process. The same framing that goes over UDP goes through the in-process Loopback queue. Any local message can become a network message later without rewriting.

Topologically-ordered, change-driven system execution. Each system declares two things as part of its type definition: the set of component types it reads from, and the set of component types it writes to. The single hard constraint underneath those declarations is that only one system in the service is allowed to claim write access to any given component type. From those declarations the framework builds a DAG, validates the absence of cycles, and produces a startup execution order. So far this is a standard ECS pattern.

The non-standard piece — and the one architectural shape in MECS I am willing to claim as my own contribution rather than as my application of an existing idea — is that the same declarations make systems skippable at runtime. Because every component mutation flows through the delta broadcast, and because each system has a known set of read inputs, the framework can ask before executing a system whether any of that system’s input component types have actually changed since the last tick. If none of them have, the system has no new input to process and therefore no new output to produce, and it does not run. Most frames have a substantial fraction of systems that meet this condition. The result is a fixed-cost startup-time topological sort paired with a per-frame change-detection pass, and the runtime ends up spending cycles only on systems that actually have work to do. The performance gain from that single design choice has been significant on every game I have run on this engine.

The same DAG carries one more piece of information I have not yet exploited: independent branches of the graph could be executed in parallel without contention, because the single-writer rule guarantees there is no shared mutable state between them. Parallel system execution is a direction I have not yet taken. The change-driven skip is what runs today.

Most of those patterns are not original to me. The games industry has been using versions of them for decades. The one I would actually claim as my own contribution is the change-driven skip optimization made possible by the single-writer-per-component-type rule described above — a shape I have not seen elsewhere and that produces a real per-frame performance win. What is otherwise mine is the integration: a single C# codebase that runs the same deterministic simulation across Unity, headless .NET, terminal clients, and AI-driven clients, with authoritative-server multiplayer baked in from the architectural level, on top of a set of design choices that have stayed stable since 2020.

Why a custom ECS at all

The obvious question is whether any of this needed to be custom. Unity has DOTS. Bevy has its own ECS. Entitas exists. Several smaller frameworks exist. Why not pick one and ship.

The short answer is that I needed the engine to run identically on the Unity client and on a .NET backend, with the same component schemas, the same system code, and the same wire format. None of the existing options solved that cleanly enough at the start. The simulation logic, the type system, the system ordering, and the network framing had to be one codebase running in two environments. That requirement narrowed the search until building it myself was the most defensible option.

The longer answer is the thesis from late 2020. The engine I wanted is opinionated in ways a general-purpose ECS cannot be. Components are unmanaged-only because the messages that carry them are unmanaged-only because the wire format requires it. Systems declare control and dependency relationships because the topological sort needs them. The whole framework leans into a specific shape of game — authoritative-server multiplayer at high per-host density, deterministic simulation, viewer-independent state, fast iteration cycles. That shape rules out a lot of other shapes. A general ECS has to support all the shapes. I only need to support one well, and the cost of not being general is paid back in the parts of the framework I do not have to write.

I would build it again.

Why C#?

The other obvious skeptical question is the language choice. C# is not the language you pick if your primary criterion is raw performance. It carries garbage collection, JIT compilation, and a runtime heavier than the C and C++ stacks most performance-critical multiplayer engines run on. I picked it anyway, for reasons that started partly emotional and became defensible engineering reasons over time.

The starting reasons were three. First, I enjoy writing C#. After decades of professional work that included substantial time in C++, I had come to prefer the language’s syntax, its tooling, and the day-to-day experience of operating in it. Personal preference is not an engineering argument, but it is the argument that determines whether I will still be working on this framework five years later. Second, Unity uses C#. If MECS was going to run on the Unity client side and on a .NET backend as one codebase, picking the language Unity already supports was the obvious move — and I had no interest in committing to writing a renderer of my own when Unity, Godot, MonoGame, and FNA all existed. Third, a .NET backend is materially cheaper and easier to operate at scale than a native-code backend. Build pipelines, deployment, debugging, observability, the surrounding ecosystem — all of it is more mature for managed languages now than it was a decade ago, and most of the cost of operating a multiplayer game lives in that ecosystem rather than in raw CPU cycles.

The honest counter-argument is the performance gap. C# code naively written runs slower than C++ code naively written, and for some workloads the gap is large. That ceiling was the original motivation for the late-2022 unmanaged rewrite described earlier in this post. Once components became unmanaged structs in explicit memory layouts and Span<T> was used aggressively across the hot paths, most of the gap closed. The framework approaches native performance not because C# is fast by default but because C# can be fast if you stay in a narrow lane of the language.

The deep-dive resource that pushed me over the threshold on “can C# really run this fast” was a personal website maintained by Jackson Dunstan — jacksondunstan.com. Dunstan publishes a body of careful, benchmark-driven writing on optimizing C# performance, almost all of it backed by hard data. I encountered it in late 2022 and read everything he had written. I came out the other side convinced C# was a defensible choice for the kind of engine I was building. If you are evaluating C# for high-performance work and the conventional wisdom in your head says C# is slow, that site will rebuild your priors. Credit where it is due.

Where this leaves us

By the end of 2023, MECS was a battle-tested engine in search of a production environment to prove itself in. The architectural decisions that came out of two decades of multiplayer game work had been re-expressed inside a modern C# codebase, the unmanaged rewrite had landed, the cross-project hardening had been done. What was missing was a real game with real network traffic against a real production backend.

That arrived in October 2023, with a publishing contract for a real-time multiplayer game on a cloud gaming platform. The engine ran into the network layer at production scale for the first time. The next post in this series picks up there: the UDP transport, the backend services that grew around the engine, the asset pipeline, the year MECS stopped being shared game code and became a platform.

The post after that turns to the AI-era developments — the source-generator migration, the compile-time diagnostics, the NuGet breakout that happened earlier this year — and the argument that making a framework AI-legible is itself an engineering discipline worth doing deliberately.

This first post is the foundation. The engine has been twenty-three years in the making and six years on disk. The rest of the series is what those decisions enabled.