Part 3 of 5
Four Engines, Fifteen Years
The Rewrite That Mobile Forced
Two posts into this series, the engines have been getting structurally better on their own terms. This one changed because something outside it changed. The puzzle game that shipped on Steam around 2010 was rebuilt for iOS in 2014 and published by DeNA, and the rebuild ran on a third engine, Carbon Monoxide.
Not a port. The game code is new, the engine underneath is new, and the reason is that mobile turned three things that had been constants into variables.
Fifteen Screens and a Base Class
This codebase has a screen for everything. Intro, cube intro, intro animation, splash, main menu, level select, difficulty, options, tutorial, in-game, pause, post-game, editor, reset save, and a Facebook prompt, all inheriting from a shared menu base and, under that, the engine's own cmScreen.
The PC version had modes. This has a state machine with fifteen named states and two layers of base class defining what a state must do: MenuBaseScreen, which nothing ever instantiates directly, and the engine's cmScreen beneath it.
Mobile is why. A phone game gets interrupted constantly, has to resume where it left off, and lives inside a platform that will suspend it mid-frame. Every one of those transitions is a state change, and a codebase with implicit modes can't describe them. Making each screen a class with an explicit lifecycle turns "what happens when a call arrives during the tutorial" from a bug into a question with an addressable answer.
Average file size here is 184 lines across 112 files. That sits between Gumball's 475 and Manic's 73, which is roughly what a codebase looks like when the decomposition is driven by a domain concept rather than by library boundaries.
Resolution Stopped Being a Number
The engine has a cmAnchor type, and its existence marks the single biggest change in how this codebase thinks compared to its predecessor.
On PC in 2010, the game could treat resolution as something read once at startup and largely ignored, because the aspect ratio landed in a narrow band and the window didn't change shape. On iOS in 2014 there were several device sizes and two orientations, and there was going to be a new size next year.
An anchor expresses where a thing sits relative to something else rather than where it sits in pixels. Once that type exists, layout becomes data the engine resolves at runtime instead of coordinates baked into the code. That's not a rendering improvement, it's the difference between supporting a new device by adding a configuration and supporting it by editing every screen.
The same pressure produced cmFontAtlas and cmFontGlyph. The PC engine rendered text through FreeType more or less directly. Packing glyphs into an atlas means text draws in far fewer calls, which matters much more on a mobile GPU than on a desktop one. Immediate-mode drawing, which the first engine used everywhere, isn't merely dated here. It isn't available.
Three Renderers, One of Them Blind
The renderer is an interface with three implementations: cmRenderGL, cmRenderGLES, and cmRenderNULL. The platform layer is split the same way, into cmPlatformWindows and cmPlatformIOS, selected at compile time.
The desktop and mobile backends are the obvious pair, and they're what makes one game run on two very different graphics APIs without the game code choosing. The interesting one is the third.
cmRenderNULL is a real implementation, 274 lines of it, that accepts every call and draws nothing. A null backend means the game can run with no GPU at all: timing a simulation without the renderer in the way, running logic on a machine with no display, or isolating whether a problem is in the game or in the drawing.
Compare that with the first engine, where drawing was glBegin calls emitted inline from game objects. There was no interface to implement, so there was nothing a null renderer could have been. The ability to run headless isn't a feature that gets added to a codebase like that, it's a consequence of having separated two things years earlier.
That decision is also why this engine outlived the game it was written for. A renderer behind an interface can gain a backend. A renderer made of inline draw calls has to be rewritten.
How Much of the Codebase Belongs to Someone Else
The third-party list is where a commercially published mobile game stops resembling a personal project. This one integrates a social SDK, an advertising SDK from the publisher's own network, and a gameplay video recording SDK, alongside the audio codecs.
That's a category of dependency the earlier engines never had. The Steam integration in the first engine was one platform's API, adopted once. These arrive as a set, each with its own initialization order, its own callbacks into the application lifecycle, and its own opinion about when it may present something over the top of the game.
There's a screen in the list called FacebookAskScreen, and it's the leftover from this. A screen exists in the game's state machine whose entire job is asking for a social permission, because the integration needed a moment in the flow and the flow is made of screens. An SDK requirement became a game state.
My opinion is that this is the part of mobile development that's hardest to plan for and easiest to underestimate. The engine work is bounded and knowable. The integration work is not, because it's driven by other people's release schedules and by business requirements that arrive after the architecture is set.
What Carried Over, and What Didn't
The manager pattern came across. There's a cmAudioManager, and the naming conventions across the GUI types show the same instincts as the previous engine.
What didn't come across is the assumption that the engine and the game share a codebase. cmGUI, cmGUIButton, cmGUILabel, cmGUIImage, cmGUIOptionButton, cmFramebuffer, cmColor are engine types with a prefix, consumed by game code that doesn't define them. The cm prefix is doing what the Manic prefix did, and it's doing it across a boundary that would eventually become a separate repository.
The engine did not come first.
Critical Mass 2's first commit is 2014-05-03, and it already contains cmPlatform.cpp, cmPlatformIOS.cpp, cmPlatformWindows.cpp, cmMath.h, cmMatrix.h, and cmVector.h. The prefix and the two-platform split are there on day one, inside the game project.
Carbon Monoxide doesn't become its own repository until 2014-11-06, six months later, and the engine directory in that first commit holds 64 source files. Every one of them already existed in the game on the same day, and 58 of the 64 are byte-identical, matching at the level of the stored object rather than by inspection. The six that differ are the predictable ones: cmFont.cpp, cmMath.cpp, and the four platform and rendering backends, cmPlatformIOS.mm, cmPlatformWindows.cpp, cmPlatformWindows.h and cmRenderGLES.mm. The files that had to know something about where they were running are the files that had already drifted.
So this engine is a program that grew a library out of itself. The seam was drawn on day one and enforced six months later. cm marked which code belonged to the engine, written into filenames, and the repository split is the point where something other than discipline started holding it.
The previous engine's static libraries and the one before that's filename prefixes took a rule out of convention the same way. What differs here is that the convention was laid down in advance. Naming a boundary before anything enforces it is cheap. Enforcing it afterwards is a set of renames and a build file.
That thread continues in the next post. This engine is the same one that, a decade later, powers a game built almost entirely out of generated code and scripts.
What Mobile Taught the Engine
All of it outlived the platform that forced it.
Explicit states beat implicit modes, because an interruption has to land somewhere and a named state is somewhere. Layout wants to be relative and resolved late, because the thing it's relative to keeps changing. And batching isn't an optimization to apply once something is slow, it's a constraint that determines what the drawing API can look like at all.
None of those are mobile lessons in the end. They're the lessons a constrained platform teaches faster than an unconstrained one lets a person avoid.