Introduction and background
Last night, I released a new game into the wild: The Endless Storm of Dagger Mountain. I submitted it to Crossroads Jam 2024, a statewide game jam sponsored by the Indiana Gamedevs community.
This game scratches a creative itch that I've had for over two years: what happens when you apply Apocalypse World style rules in a digital game? I've had this as a component of a few different design explorations, none of which bore any fruit—sometimes because they weren't fun and sometimes because their scope exploded. In fact, in May, I started work on a project that was growing too large, and it included PbtA elements. By the last week of May, I had put that side project to rest. I decided that I could use Crossroads Jam as an excuse to isolate just this single design idea—digital PbtA—and package it up into a jam-sized game. Readers may be interested in looking at my previous exploration of tabletop PbtA, which took the form of Kapow! The Campy Superhero Role-Playing Game. I also wrote an essay comparing the math of PbtA and d20 systems.
I was a little disappointed that the theme "severe weather" won the polling on the Indy Gamedevs Discord. Since this is the first Crossroads Jam, it seemed like a great opportunity to highlight something positive about the state. You know, like corn. More seriously, there are a lot of great things about Indiana, including globally-recognized events like the Indianapolis 500. And corn. But I digress, and others preferred "severe weather." I had been wanting to explore some pulp fantasy writing a la Robert E. Howard's Conan stories, which I read a few years ago. This presented a good opportunity: a lone, stoic hero, making a long journey up to the top of a mountain where dark magic has brought about the destruction of the innocent.
Game and Narrative Design
Most of the writing is really just a first draft. Despite years of gamedev, I have done barely any game writing. It felt good to get my hands dirty and create enough content to carry the gameplay. I estimate I spent about 15 hours just writing content for a game that takes a few minutes to play. The writing was enjoyable, but especially as I got tired, I couldn't shake the feeling that much of the text was low stakes. Each dice check has at least three paths—succeed, succeed at a cost, and fail—and I tried to keep them equally interesting. I do not spend a lot of time with interactive fiction, but I quickly ran right into the same problem that any narrative designer has: with chance or with agency comes the loss of authorial control. There are a few specific scenes in the game that I would like to spend more time on, to make the story more compelling and to evoke Howard more strongly.
I was not able to pull in all of the tabletop inspiration that I originally wanted. In tabletop games, I love the idea of the Countdown Clock or what Runehammer calls Timers. A simple timer that ticks down becomes a source of tension for the player and, in the system, it's another formal element to manipulate. In my first draft of Dagger Mountain, I had a timer that, if it ran out, would cause the game to end before the player could summit the mountain. This worked well as a penalty, especially when asking the player to choose between attribute reduction and advancing the timer. However, the game ended up being too short to make the timer meaningful. In fact, one of the problems that inspired me to think about removing the timer was the trouble of visually representing a "timer" as something with only two or three units. I could not reasonably balance it and give it a significant value: a timer with two clicks feels more like it's depleting some other resource rather than feeling like time.
The other common PbtA element that I didn't add was what Apocalypse World calls "reading a sitch." The idea here is that a player can spend an action trying to understand a situation, where success or partial success determines how many questions they can ask about it. Systematically, this is simple: I could have a preconstructed list of questions and answers, and these could provide lore and setting information. As I got into building the narrative, this felt like it would not have a good return on investment: every other decision produced changes in the world state, such as modifications to attributes, and I didn't want "reading the sitch" to be a wellspring of mechanical benefits. It would be relatively easy to add this into my software since it was in mind from the beginning, but it did not find its way into this project.
Speaking of attributes, I still have something of a pipe dream that one could make an RPG attribute system that is consistent with Thomistic philosophy. Consider that the legacy of Dungeons & Dragons presents a sort of dualism, that the mind and the body are separate. Yet, as confirmed by a recent conversation with a weight-lifting friend of mine, the two must work together: it's not clear that Strength (as physical might) and Wisdom (as willpower) are independent variables, for example. I couldn't find a way to distill a more Aristotelian view of the human person into three or four attributes, but reviewing Apocalypse World's attributes, I was reminded that they describe how one does something rather than what someone is. That's a great hook for future design work. In the meantime, for this project, I made a list of actions that I wanted the player to perform, knowing the genre and setting, and I categorized these into the three attributes that are in Dagger Mountain: bold, determined, and savvy. I admit that there are a few stretches in the game where penalties might be hard to classify in these ways, but I will be curious to hear what players think about them as a trio.
Technical Considerations
Inspired by Knights of San Francisco and the beauty of Dart, I started writing Dagger Mountain in Flutter. It's a beautiful way to write applications, but there's a significant difference between declarative and imperative UI programming, and I find that I stutter a bit when I hop between them. I set up the essential architecture and was enjoying myself until I tried to implement some UI features, specifically the scrolling list of text and buttons along with placeholder animations. There is a lot of typing required, animations were hard to debug, and it wasn't always obvious where my problems came from. I am sure there was a lot to learn from the endeavor, but I was on a deadline and wanted to get things up and running quickly. In transitioning back to the comfort of Godot Engine, I realized something: while dart's asynchronous programming features are wonderfully expressive, there is a real power in GDScript's simple signal syntax. It is hard to get more terse than that, although it comes at the cost of not having explicit control over things like Futures. I returned to Godot Engine, re-creating everything I had written in dart in very little time. With the design decisions made, I just had to interpret it in the new environment and type it up.
I spent too much time in Godot Engine adding dynamic font resizing. I knew I wanted the game to run comfortably on a desktop or mobile browser, and giving the player control of font size seemed the best way to do this. It required a lot of shenanigans with theme overrides, and as I added more visual elements such as the visible dice, it got more and more convoluted. Near the end of development, I just gutted this feature from the play experience and put a configuration on the main menu, which allowed me to just fiddle with the values in the main theme rather than deal with distributed theme overrides. What really irked me was when I started working on deployment, realizing that the cleanest solution for the player would be to use the browser's built-in font rendering and resizing... the way that a Flutter app would have done. Sigh.
Here are a few summary observations along these lines. Flutter is great for its static typing, robust asynchronous programming support, autoformatting, built-in browser font resizing support, spread operator, null safety, and most importantly, refactoring support. Godot Engine clearly wins on terseness of signals and tweens and the ability to rapidly build and test scenes independently of each other. To clarify that last point, I regularly decompose my Godot Engine programs into scenes that I can run and configure by themselves, confident then in how they will work when instantiated as part of a larger system. In Flutter, I wish I could easily say, "Spin up one of these widgets by itself and let me tinker with it," but I have not found anything that comes close to Godot Engine's rapid development support this way.
Incidentally, I did briefly consider other options than writing my own engine. I am intrigued particularly by ink, which I have never used. I was hesitant to jump into something with such a different syntax, although I am sure I could learn a lot from it, too. What killed the deal for me though was that it wasn't clear to me that I could easily plug in the PbtA aspects that I wanted. I discovered a Godot Engine integration, so perhaps I will investigate that later this summer. It wasn't until my family was testing the game that one of them mentioned Dialogic, which I haven't used since Godot 3.x. I haven't looked at it to see if it could have been modded for my purpose. However, writing for Dagger Mountain made me appreciate why narrative designers need better tools than just piles of scripts and a notebook sketch.
Dagger Mountain is my first released game that uses an event queue to isolate the game rules from the interface. I have tinkered with this pattern in several abandoned projects. Two summers ago, I spent a lot of time studying egamebook and its architecture, and I learned a lot from it even though that particular summer project was never released.
My approach separates the software into three parts. The module is the content of the adventure itself, the story of Dagger Mountain. Each scene in Dagger Mountain is a GDScript file that is given a reference to an Adventure object. The next part is the rules engine, which is manifest in an Adventure object. The scene is given a reference to an Adventure object, and the module tells it to do things like show text, modify attributes, or present a series of choices to the player. Internally, the rules engine generates events to correspond to these interactions, posting them to the event queue. The final part is the presentation layer, which subscribes to the event queue. It dequeues events, processes them, and then notifies the rules engine when it is complete. The code is all free, so feel free to look at the prelude scene for an example of how this works.
I decided early in the project that I would repurpose GDScript as my narrative scripting language rather than create an independent data format that would be interpreted. The primary reason for this decision was the pressures of time: GDScript is already a scripting language, so using its support for functions and conditionals would be faster than writing my own. This is true, but I hadn't considered all of the costs at the time. The game runs through function calls in the module layer, which is nice for terseness but actually makes it hard to test in a modular fashion. I am sure that if I had used TDD, I would have had a more testable architecture. I would much rather have test coverage of the whole state space of the game; instead, I have to hope that my manual testing was adequate.
I did add integration tests near the end because of the need to await basically every call in the module layer. Missing a single instance will break the player experience. I wrote a test that reads through the module layer and looks for cases where await is missing. It took a little time get the test working, but it immediately found a case that I had missed, so that was worthwhile.