How a static BattleTech board game got animated mechs — without me becoming an artist (and avoiding "AI slop" as much as possible).
A few readers of this blog will know that in my spare time I'm building my own digital version of BattleTech known as MakaMek. And usually that's the only topic I'm motivated enough to write about. But I haven't posted anything in a while. They say that "coding is solved" so why should I even bother? 😄
At the same time you always want to have some fun, if coding is no fun anymore — the solution is to look somewhere else.
One of the features I have wanted for MakaMek for as long as the project has existed is unit animations. So is it possible for me to become an artist?
The art is the weakest part of any app I have ever made. With MakaMek I was lucky enough as another (much older and bigger) implementation of BattleTech — MegaMek — was around for decades. Dozens of contributors added thousands of unit assets — literally everything you can think of in the universe. I leverage this effort by using MegaMek's artwork in my game. But a unit in MegaMek is a single 84×72 top-down image. Functional, readable, and completely unusable as a source for animation. You can't rig a PNG. Or you can try with AI, but AI-generated sprites are not something I wanted to do. I needed a proper deterministic pipeline that would allow me to reliably produce the same sprites every time I run it. And the AI could have a boring part of the job — to babysit the pipeline while it runs.
An old trick
So I remembered something obvious: games have been faking 3D with 2D sprites for decades.
MechCommander, one of the first BattleTech games I played, did exactly this in the late 90s. The mechs were 3D models rendered from fixed angles and turned into sprites. They looked heavy and believable because the silhouette came from a real model.
And to be honest I did exactly the same thing somewhere in 2007 with "white" and "blue" dice for Magical Yatzy.
So yes, rendering 3D models into 2D sprites is an old technique, but you need a source of 3D models — and a way to automate the process, otherwise rendering even a tiny fraction of units I support currently would take forever.
The source
Piranha Games has been producing BattleTech units since MechWarrior Online, and those designs are now part of the visual vocabulary of the franchise. They show up in MechWarrior 5, in HBS's BattleTech, and in multiple fan-made animations like Hired Steel (I really recommend this one by the way). Whatever you think of the games, the models are excellent: detailed, consistent, and instantly recognizable (thank you @Flyingdebris).
But the important part for automation is the structure. MW5's assets are named consistently and organized with a proper machine-readable structure in Unreal Engine packages. Mods extend the same structure with more units. The only problem is that I've never worked with Unreal Engine and had no idea how all that stuff works, but that was exactly the part I was ready to delegate to AI.
Evening one: the render that didn't look right
I started with one deliberately small question: can a single MW5 Atlas, exported by hand (I used an open-source app called FModel) and rendered headlessly in Blender at board size, look comparable to the current MegaMek art?
The answer was no. At least not at first.
The first 84×72 render was a gray, busy mass. It read as "some mech" but was almost indistinguishable from noise: the model's panel lines and greebles were smaller than a pixel. And the head was missing entirely, apparently in MW5 the cockpit is a separate mesh as well as all the weapon systems and equipment. MegaMek's hand-drawn sprite had more personality and was way more recognizable.
I kept playing with it and came up with a few improvements that worked: render at 4× and downsample, real antialiasing, a clean one-pixel outline. I also stopped rendering at fixed 84×72 size (MegaMek legacy), each mech should get a canvas fitted to its own silhouette.
Behind those fixes was a decision not to build a custom renderer. Blender (another tool I've never touched) does the rendering; the pipeline only drives it. That kept the experiment small enough to finish in one evening.
I tested the final rendering script on a second unit — the Annihilator. That second image also gave me a useful surprise: the pipeline isn't uniformly good or bad. The Atlas is close to a worst case — broad shoulders, arms held wide, little depth. The Annihilator reads much better from directly above because its plan view is deeper. Readability is chassis-dependent, which means the pipeline probably needs a review pass on different models.
Evening two: teaching the agent to open the box
Hand-exporting one (or eventually two) mech skeletons is one thing, but hand-exporting hundreds of fully assembled variants is a lifestyle choice I wasn't ready to make.
So the next question was: can the agent read the game's pak files itself?
MW5 runs on Unreal Engine 4.26. Its assets live in a 100 GiB pak with 285,997 virtual files. The agent built a small C# tool on top of CUE4Parse, the open-source library behind FModel, and started exploring. By the end of the task it could mount the game, enumerate all the chassis, classify every body, cockpit, glass and weapon mesh, and dump the custom hardpoint and loadout data.
The tool read the loadout, followed the weapon tag, found the hardpoint visual, and placed the mesh on the right bone. AS7-D came out with an AC/20, LRM20, SRM6 and medium lasers. AS7-RS, same chassis, came out with an AC/10, LRM15, SRM4 and large lasers. Visibly different and "canon" correct at the same time.
That was the moment the project stopped being a rendering experiment and became an asset pipeline.
The agent used CUE4Parse to read the pak, Blender to render previews and final frames, Python scripts to inspect meshes, and later SharpGLTF to write animation because CUE4Parse can't export animated glTF. It picked tools the way a person would: whatever gets the job done and can be checked.
Evening three: making it walk
Static mechs are nice. Walking mechs were the whole point of starting this adventure.
Animations turned out to be their own little archaeology project. The body mesh and the animations are separate assets in MW5 models. Bone indices don't line up between the shared skeleton, the chassis mesh and the clip, so everything has to be mapped by name. The walk clip translates the root by 8.6 meters even though the file says root motion is locked. The source is 30 fps with 61 keys for a two-second loop, and the closing key duplicates the first pose.
The agent found all of this by probing the data in the pak. Then it did the unglamorous part: sample the loop at 12 fps, strip the root motion, render ten frames, pack them into a spritesheet, and build a gif file so I could review the whole cycle in one image instead of opening ten PNGs.
The result reads as walking. Not because the legs move a lot — from directly above, a forward step is barely visible — but because the feet alternate and the body shifts its weight by about five pixels. That's enough. The eye fills in the rest.
Unfortunately I couldn't get everything I wanted and needed for MakaMek: MW5 never shipped knockdown, so there are no prone or stand-up animations to extract. I'll decide what to do with it later, but even if I don't — it still would be better than it was.
Evening four: putting it on the board
The last evening was integration into the game.
MakaMek's unit packages are zip files with a manifest, a JSON definition and a PNG. I extended the format additively: v1 packages behave exactly as before, v2 packages can carry an optional spritesheet and metadata. If the spritesheet is missing or broken, the unit falls back to its static image. No unit ever disappears because an animation failed.
On the runtime side, a received move command now plays over a fixed five seconds (will be configurable or disabled via settings). Position, facing and sprite frame share one linear clock. The frame is driven by distance traveled, not time, so the feet stay planted at any speed.
And yes, the first in-game version had the mech walking backwards. The model's forward axis and the board's facing convention disagreed by 180°. One re-render later it was fixed.
What the agent actually did
I want to be precise about this, because the hype version of this story is misleading.
The agent did not have the idea. It did not decide that the Atlas should read as a heavy mech, that the outline should be one pixel, or that a five-second move feels right. It did not look at the first render and say, "the head is missing, and that matters."
What it did was carry the work. It read unfamiliar APIs, wrote the extractor, the Blender driver, the spritesheet packer, the package reader, the playback timeline and the tests. It ran renders, measured the results, compared frames, and wrote the docs. It kept going when the eleventh API call failed. It did in an evening (while I was cooking dinner) what would have taken me weeks of context switching, Blender tutorials and swearing.
I brought taste, context and acceptance criteria. The agent brought patience, recall and throughput.
The workflow that made it work was simple: every step had to produce something I could look at — a render, a contact sheet, a JSON report, a diff. The agent could explore freely as long as it came back with evidence.
The honest limits
- Licensing. MW5 models and renders derived from them are PGI/Microsoft IP. The tooling can ship; the art cannot. Everything I rendered lives in a gitignored folder and is loaded as a local asset pack.
- Coverage. This is one chassis, end to end. MW5 has 104 chassis; MakaMek has 199 variants. Batch rendering is a matter of hours, but reviewing the output is still human work.
- Missing animation. Prone, stand-up and idle breathing don't exist in the source. They need to be authored, not just extracted.
- The agent is confidently wrong sometimes. It once scaled the outline thickness twice and turned a 4× render into a black blob. The lesson is the same as with code: look at the output.
Why this is bigger than my hex map
The interesting part of this project isn't that an AI wrote some C#. It's that the same loop applies to almost any task, including ones you would never call software development. An agent is not a code generator with a chat window. It's a process executor that happens to be very good at code. The loop looks like this:
- Find a source of truth.
- Build a small tool to read it.
- Visualize the result.
- Compare it to what you wanted.
- Fix the gap.
- Automate the repeat.
That loop is not coding. It's how you get anything done in an unfamiliar domain.
The takeaway
Four evenings. One walking Atlas. A pipeline that can, in principle, do this for every mech I can legally render.
Would I have done it by hand? Maybe. In a few months, with a lot of Blender tutorials and a lot of swearing.
That's the real productivity gain. Not ten times more code per hour — a lower barrier to starting something you don't know how to do yet.
Next: the other 198 variants, authored prone and stand-up clips, an idle that breathes, and a Locust that doesn't look like a coffee table.
Behind the scenes
The whole thing is documented in the open, including the dead ends:
- #1642 — first render, static Atlas
- #1652 — headless extraction and assembled variants
- #1653 — animation clips and spritesheets
- #1659 — mmux v2 and in-game playback
The pipeline, in one line:
MW5 pak → MW5Extractor → assembled glTF → headless Blender → spritesheet → .mmux v2 → UnitControl
If you want the technical version, the design and the extraction notes are in the repo under docs/architecture/asset-pipeline.md, docs/architecture/blender-render-driver.md and docs/analysis/mw5-model-extraction.md.







Top comments (0)