Rendering in Retrospect · #10 of 12

Why I'm Building a Custom Engine Again

Retrospective — written in 2026, looking back.

A Question I Keep Getting Asked, Mostly by Myself

Every few months I ask it again, usually at midnight, usually while debugging something an off-the-shelf engine would have given me for free: why are you building a custom engine for a personal game project? You know how much work this is. You know where the bodies are buried — in the asset pipeline, in the editor, in the thousand small platform details.

This post is my honest answer, written while the project is still in planning and early structure rather than finished. The project is an autobattler: two armies are assembled before a fight, then the battle plays out in real time with many units acting on their own. I do not control units during the battle. I design the composition, press start, and watch the system I built resolve it. That specific shape is the whole reason a custom engine makes sense here — and also the reason it would make no sense for most other games I can imagine making.

What an Autobattler Actually Stresses

An autobattler sounds simple until you list what it asks of an engine.

First, many units at once. Not thousands of static instances — hundreds of individual agents, each with health, cooldowns, target selection, movement steering, and animation state, all interacting. The bottleneck is rarely drawing them. It is updating them without the frame cost exploding as unit count rises, and keeping that update orderly enough to debug.

Second, battles need to be explainable after the fact. When a fight goes wrong — when one unit inexplicably walks past three enemies to attack a fourth — I need to know why. That means deterministic simulation matters to me in a way it does not for most action games. If I can replay the same battle from the same seed and the same initial composition and get the exact same result, I can bisect a bug. If the result drifts run to run because of floating-point order, frame-dependent timing, or hash-map iteration order, debugging becomes archaeology.

Third, the interesting design work is in systems interaction: positioning, targeting rules, ability timing, status effects. The visuals serve that. I need rendering that is clear and readable — who is winning, what just hit what — more than I need rendering that is spectacular.

An off-the-shelf engine can do all of this. The question is how much of its machinery I would be fighting, bypassing, or simply never using to get there.

The Honest Case Against Off-the-Shelf, for This Project

General-purpose engines are built around assumptions: variable frame time driving gameplay, object hierarchies with per-object update methods, physics as a central service, an editor-first workflow for level-driven content. Those assumptions are good ones for most games. For this one, several of them point the wrong way.

Determinism is the sharpest example. Achieving a bit-reproducible simulation inside a general engine means swimming upstream — fixing timesteps, avoiding the built-in physics for anything gameplay-relevant, being careful about every unordered container and parallel job whose completion order leaks into results. It can be done, but I would be spending effort neutralizing the engine's conveniences.

The second issue is weight relative to need. I do not need terrain, cinematics, world streaming, or a visual scripting system. I need a fixed-step simulation, a compact data layout for units, and a renderer I understand end to end. Every unused subsystem is not just disk space; it is concepts I have to keep in my head while debugging, and upgrade risk I carry for features I never touch.

And the third reason is the least respectable and the most true: building the engine is part of the point. This is a personal project. Part of what I want from it is to understand, by constructing, how the pieces I have used for years fit together when there is no one else's abstraction in between. That is a legitimate goal for a personal project in a way it would never be for a product with a deadline and other people's money attached.

Simulation and Rendering, Kept Apart on Purpose

The central architectural decision is a hard separation between simulation and rendering.

The simulation runs at a fixed timestep — say, purely as an illustrative example, 30 steps per second — using integer-friendly, order-stable logic wherever determinism matters. It owns units, positions in simulation space, health, targeting, and abilities. It knows nothing about meshes, shaders, or the GPU.

The renderer is a consumer. Each frame, it takes the previous and current simulation states, interpolates between them based on how much accumulated real time falls between steps, and draws. If the display runs faster than the simulation, motion still looks smooth. If a frame hitches, the simulation does not spiral — accumulated time is clamped, and the worst case is slow motion, not different outcomes.

// Fixed-step simulation with interpolated rendering (illustrative structure)
double accumulator = 0.0;
const double kStep = 1.0 / 30.0; // simulation step, not a measured value

while (running) {
    double frameTime = Clamp(GetFrameSeconds(), 0.0, 0.25);
    accumulator += frameTime;

    while (accumulator >= kStep) {
        sim.Step(kStep);          // deterministic: seed + inputs only
        accumulator -= kStep;
    }

    double alpha = accumulator / kStep; // 0..1 between prev and current state
    renderer.Draw(sim.PreviousState(), sim.CurrentState(), alpha);
}

This separation pays for itself constantly. I can run the simulation headless — no window, no GPU — and play a thousand battles overnight to check balance: which compositions win, how often, by how much. I can record a battle as nothing more than a seed plus the two starting compositions, a tiny file, and replay it exactly. I can write unit tests against simulation rules without standing up a renderer. None of that requires heroics; it falls out of refusing to let rendering concerns leak into the step function.

What This Engine Tries to Be — and Does Not

Scope discipline is the only thing that makes a one-person custom engine survivable. So, explicitly:

It tries to be: a deterministic, fixed-step battle simulator; a data-oriented store for units where the hot loop walks contiguous arrays instead of chasing pointers; a straightforward forward renderer with clear, readable visuals, instancing for the many similar units on screen, and the post-processing chain I wrote about in the previous post; and a small set of tools I actually use — a battle replay viewer and a balance-run harness.

It does not try to be: a general game engine, an editor platform for other people, a physics engine, a scripting environment, or a showcase for cutting-edge rendering features. Animation is functional, not a state-machine cathedral. Audio is someone else's library. There is no networking design beyond the observation that a deterministic simulation with recorded inputs is the right starting shape if lockstep play ever became interesting — a design topic I think about, not a feature I claim.

Data layout deserves one concrete note, because it is where the many-units requirement bites. A unit's per-step update touches position, velocity, health, target, and cooldowns. Storing those as separate contiguous arrays — structure of arrays — means the targeting pass streams only the fields it needs, and adding a unit type is adding data rows and a behavior entry, not subclassing an object hierarchy. When I profile (more on that habit in the next post), I want the cost of a battle to scale in a way I can reason about from the data, not discover from a profiler surprise.

The Risks I Am Choosing With Open Eyes

Custom means the boring parts are mine: window creation, input quirks, asset loading, build configuration on more than one machine, the crash that only happens on a different GPU driver. There is no forum post with my exact problem. There is also a real risk that engine work cannibalizes game work — that I spend a month polishing a renderer feature the battles did not need because polishing renderers is what I know how to do. My guardrail is to write the battle rules first, keep the renderer deliberately a step behind the simulation's needs, and let an ugly-but-playable fight embarrass me into the right priorities.

So, Again

I am building a custom engine again not because off-the-shelf engines are bad — they are extraordinary — and not because I think I can out-build them. I am building one because this particular personal project has an unusual center of gravity: deterministic, testable, many-unit simulation, with rendering in a supporting role. Building to that shape, and learning what I learn along the way, is the project. The autobattler is the excuse and the test case. If it ever stops teaching me or stops being fun to watch fight itself, I will know the experiment has run its course.