LDK Is a C Game Engine That Refuses to Be Universal
Marciovmf built his own engine from scratch for his own games. The interesting part is how the build system enforces that decision.
- LDK is a C game engine built for exactly one user, and its README says so plainly instead of dressing the constraint up as a roadmap.
- The same game source compiles into two shapes: a DLL the editor loads and a statically linked monolithic launcher, so the editor hosts the engine rather than duplicating it.
- The build system is where LDK's opinions actually live, from a configure-time MSVC runtime guard that refuses to mix CRTs across a DLL boundary to a hand-written PE header parser.
- Writing software for one person is a design position with real trade-offs, not a smaller version of writing it for everyone.
An engine with one user
LDK (Ludum Dev Kit) is a flexible game engine being developed from scratch in C for my own games.
That sentence is the whole project. LDK is a 3D engine for Windows with an OpenGL 3.3 renderer, an ECS core, a scene and system model, a visual editor, and a CMake pipeline. It is also a statement about who the software is for, and the README keeps going: it is not trying to be a universal engine, not trying to replace C with an object model, and not trying to hide the runtime behind an editor.
Read that as a list of costs and it gets interesting. No cross-platform layer. No scripting language bolted on top. No plugin system. No backwards compatibility promise. The renderer is deliberately thin, because the author says graphics programming is not his specialty and does not pretend otherwise. These are not gaps waiting to be filled. They are the shape of the thing.
Most engines are built to answer a question from strangers: will this run on your machine, your genre, your team? LDK answers a question from one person. Almost every decision downstream follows from that, including the ones you would normally never make in public. The most visible of those decisions is not a feature at all. It is the build system.
One source, two shapes
The technical centerpiece of LDK is that a game is not one program. The same game source compiles into two different forms, chosen by a build option. Build it as a shared DLL and the engine's visual editor loads it as a live module. Build it as a monolithic launcher and it is a single statically linked executable with no editor attached.
This is the mechanical truth behind the README's claim that the editor is not a second engine. It is a host. The editor does not reimplement rendering, scene management, or the ECS. It loads the same code your shipped game runs, through a DLL boundary, and drives it. There is one engine, and the editor is a client of it.
The options that drive this read almost like a sentence. OPTION_BUILD_GAME produces the DLL. OPTION_BUILD_GAME_LAUNCHER produces the monolithic executable. OPTION_LDK_USE_PREBUILT is the quiet one: it lets the game DLL link against an engine binary that already exists, so editing a game never drags the engine into a rebuild. Presets compose these into one-command choices, so switching shapes is a matter of picking a preset rather than editing flags by hand.
Rules the build system enforces
If the README is a set of opinions, the CMake files are where they get teeth. Three guards stand out, each one a lesson the author chose to encode as a check instead of writing down as advice.
The first guard sits at the very top of the top-level CMakeLists.txt, and it exists because LDK splits itself across DLL boundaries. Objects owned by the C runtime can cross those boundaries, and if two modules disagree about which runtime they were built against, the heap gets corrupted in ways that are miserable to debug. So the build refuses to let that happen. Every configuration, Debug included, is forced onto the dynamic release CRT.
# LDK DLL boundaries may pass CRT-owned objects between modules. Keep every
# configuration on the same dynamic release CRT (/MD), including Debug.
The guard does not just set a flag. It scans the compiler flags and the environment for /MT, /MTd, or /MDd, and if it finds one it fails configure with a specific message explaining why. A developer who tries to change the runtime gets an explanation, not a corrupted heap three hours later.
The second guard is the strangest file in the repo. When a game is built against a prebuilt engine, a script checks that ldk.dll and its import library are actually there. Then it opens the DLL and parses its PE header by hand: the MZ signature, the e_lfanew pointer at offset 60, the PE\0\0 signature, and finally the machine field, compared against the architecture you asked for. It then warns if any engine source is newer than the DLL, and pointedly does not rebuild. Game builds stay fast, and a game cannot quietly recompile the engine out from under a running editor.
The third guard is a project file parser written in CMake itself. A .ldk file declares the packages a game needs, and CMake reads it at configure time with no external tool. It registers the file so editing it re-runs configure, rejects duplicate entries, and refuses paths containing .., absolute paths, or anything that is not a .box package. The game's assets are fully known before the compiler runs, so the runtime never has to go looking for them.
None of this is glamorous. All of it is a promise. Each check is a lesson the author already learned, sealed into the build so he never has to learn it twice.
Comet and the annotated ECS
A visual editor needs to know about a game's components and systems. In a language with runtime reflection, the editor can just ask. C has no reflection, and a universal engine would solve that by inventing a registration layer, a schema format, or an editor-only fork of the engine that knows things the shipped game does not.
LDK solves it with annotations. The author writes //@component, //@system, and //@inspect directly above the relevant C declarations. A tool called Comet reads those comments from the source and generates the glue the editor needs. The annotations live in the code, next to the thing they describe, and a generator reads them. No reflection, no parallel definition, no editor-only build of the engine.
It is the same philosophy as the build system in a different costume. Rather than adding a mechanism the language does not have, LDK writes the intent into the source and lets a small tool translate it. The compiler does not need to understand the annotations. Something else does, at build time, once.
What LDK is up against
The temptation is to line LDK up against established engines and score the features. That misses the point. The useful comparison is not capability. It is who each project is built for.
| Project | Language | Scope | Built-in editor | Stance |
|---|---|---|---|---|
| LDK | C | Full engine for one developer's games | Yes, loads the game as a DLL | Made for my own games |
| raylib | C | Graphics, input, audio library | No | Simple API for anyone |
| SDL | C | Low-level platform layer | No | Foundation for other people's engines |
| LÖVE | Lua on C++ | 2D framework | No, but scripting-first | Accessible game making |
| Godot | C++ | General-purpose engine | Yes, editor-centered | Engine for everyone |
Look down the LDK row. Every column is thin except the last. It is Windows only. Its scope is one person's games. Its community is essentially its author. On a feature matrix that is a losing hand, and the author knows it, because he never entered the contest. The Stance column is the product.
The case for software with one user
The universal-engine era has made personal software feel like a regression. Write an engine for yourself and the first question is always when it will support everyone else. But that question assumes the universal engine is the default and the personal one is an unfinished version of it. LDK suggests the reverse. A universal engine pays for its reach in complexity, and the bill lands on every user. A personal engine pays a different price: it is useless to almost everyone, and perfect for one.
What makes LDK worth reading is not that it has an ECS or a renderer. Plenty of engines do. It is that the author's constraints are visible all the way down. A Windows-only engine can hand-parse a PE header in CMake, because there is no other platform to abstract over. A single-user engine can force one CRT, because it never has to make that configurable. The narrowness is not a limitation the design works around. It is what makes the design possible.
The build system is the last word on this. A configure-time check that fails with an explanation is a promise the author made to himself, written in a language the machine will enforce. It will not let him forget. That is what software with one user looks like when it is taken seriously: not a smaller product, but a sharper one.





