Home     Dev Logs     Team    
The Turn-Based Game Engine
Evolution by Design
Author: Ted Brown
April 26, 2024


It's been two months since I posted the first preview video of Tactician Workshop. A lot of folks asked for access to a demo, which came as a pleasant surprise. And thank you to those who asked! It means a lot to me.

But I had to decline, because I knew it wasn't ready. Given the time that's elapsed since then, I think it's fair to ask if it's ready now, and the answer is... it will be soon!

It's also fair to say, "Hey. That's working software. Why don't you just put it into Early Access and make updates along the way?" Or even ask, "What's taking so long?" Especially since I'm known to write games in a weekend.

Those are excellent questions!

Let's talk about the development time angle first, because there's a lot of healthy discussion about it right now.


Game developers know that games are "shipped, not finished," meaning that it's never done to our satisfaction before going out to consumers: it's too cost intensive, and the consumer value is too low. (of course, if you calculate this incorrectly, you're in trouble)

Even perfectionist developers (and I count myself as one) are starting to warm up to the value of jank in games. This is more of a generational thing than anything else: the games I grew up with were stringently tested, so that's my bar for games I make. But folks born in the 90's and later grew up with modern game culture, one that is more conversational with the medium (Flash games, hello). They're more comfortable with jank, and might even see it as endearing. (I'm still mostly anti-jank, though: it's like seeing a movie where there are boom mics in every scene!)

Finally, Balatro's messy source code is getting a public airing, shaming, and defense. It sold a million units in a matter of weeks, but someone opened the code and, well, it's getting "vigorously discussed." On one side you have people saying "code quality matters," on the other people are saying, "clearly not if it sold a million units."

I actually agree with the second group of people. Ship games, don't "finish" them. Be more comfortable with jank. Code quality doesn't move units.

The thing is: I'm not making a game. This is software to make games. Code quality actually counts. My customers can ship janky games all day: God bless and get that loot. But Tactician needs to be a rock-solid foundation. I want it to be as solid as a math library. If it's a risk factor for a project, then I've failed.


There's another aspect to game development that most people aren't familiar with. No matter how detailed the game design is at the start of the project, at some point the game itself becomes a creative partner. IT tells YOU what it wants to be, and how it wants to evolve. This is because games are an interactive medium that can't be captured in a fixed linear format, like a document. It has to live before it can be understood.

Tactician isn't a game, but it does share this aspect. Every development milestone is focused on implementing a specific game design: "how would I implement game X with Tactician?" And with every milestone, I gain a better understanding of what Tactician needs to be. Specifically, I gain a better understanding of how to evolve its syntax, semantics, and architecture to be a lithe and robust beast of a machine.

In other words, after a milestone, I know more about what Tactician should actually do (design), and how it should do it (code). So when I write down my next product definition, I'm able to do so with more clarity: not just around what's possible, but also around what's not possible, or is too difficult to implement with the current codebase.

When the time it would take to implement those newly defined features gets too high because the source code wasn't built with that in mind, that's when I start to consider a refactor. When I have enough of those signals, then I just go ahead and do it.


Three weeks ago, I started my sixth refactor of Tactician, version A07. This one is focused on reproducing the battles of XCOM, minus the graphical presentation.

This version is the result of a lot of signals, meaning its dev plan was highly informed by past experience. As a result, it's led to a huge jump forward in terms of syntax durability. Future refactors are much less likely to touch the lower levels of the code.

The question I always ask myself is: is this the last one? Is this the fabled "X Refactor" that sets the foundations I'm looking for? I can't afford to develop this indefinitely. So believe me, it's top of mind!


Here's an example of how a core feature has evolved with this process.

Attributes store data on entities: they're fundamental to the entire operation. Originally, they didn't have a struct or a class. They were just a key/value pair stored in a Dictionary on a Component on an Entity, and the value type was always integer. It was pretty simple, but it was durable, and it lasted for a while.

Starting in A04, attributes got their own struct so I could expand the value types to boolean, formula, integer, string, and position. Since this meant attributes could be found "away from their component," I wanted to add a clever, readable way to trace their way back. I did this by giving the name a "Component.AttributeName" syntax.

All of this was necessary and useful from a functional perspective. But by the time I got to A05, the cracks were starting to show. The overloaded attribute name required lots of special case code to manage the component name: is there, is it valid, is it missing on purpose, etc. And several Tactician features relied on attribute values (and all of their associated functionality), but weren't actually attributes, which felt extremely gross.

I could go on, but I knew it was time for a change.

In A07 (A06 being an experimental build), I split Attributes and Values into separate structs, and took the opportunity to overhaul how they interacted with the rest of the engine. The amount of code decreased while quality and robustness increased: sure signs of a good fix. This feels like something that won't change for a long time, if ever. In other words, it's a new syntactical foundation for Tactician.

The key is that I didn't arrive at this architecture by hypothetically designing what might be needed, or over-engineering something that wouldn't be used. It was developed with the pressure of a rapid evolutionary cycle that kept practical experience and long-term vision in mind.

That's the benefit of the broader refactoring process. It's not always appropriate for game projects, but I think it's absolutely essential for engines that are pre-alpha. As for the other changes, I look forward to sharing more about them in future logs.

Thanks for reading!