Skip to main content

My Marvel Snap MODOK deck

I've been playing Marvel Snap quite a bit since it came out, and I'm currently using a deck based on MODOK that I'm quite happy with.

This is what that deck currently looks like: My Marvel Snap MODOK deck

It's built to have multiple ways to deliver a ton of power in rounds 5 and 6.

It's not built to react to an opponent. I have a reactive deck, and it's fun, but I tend to lose because I can't generate enough power with it.

I don't win every single game with this and it has its vulnerabilities, but it works well, and I've never quite seen anyone use MODOK this way against me.

Here is a detailed explanation.

The concept

The concept of this deck is to play MODOK on round 5, and to generate points through Morbius, Mystique, and The Collector, and/or to build up Apocalypse and Swarm, which you then play on round 6.

The key cards

MODOK

Unsurprisingly, MODOK is the key card. If I don't draw this by round 5, or it gets hit by Yondu or something else, I tend to retreat.

Morbius

Morbius is one of the most important early cards. It generates 2 points for each discarded card. This effect is Ongoing, so it's great on Onslaught's Citadel, but vulnerable to Rogue and Enchantress.

Mystique

I play Mystique after Morbius when I can, because it duplicates Morbius's effect, meaning my power gain in round 5 is doubled. I will happily skip early rounds for the chance to play Mystique after Morbius, and I try to carefully play both Morbius and Mystique in the right order so this card's effect kicks in.

The Collector

The Collector is another important early card. It generates 1 point for each card that enters my hand, which happens when I discard Swarm, Apocalypse, or Helicarrier. It's not as powerful as Morbius, let alone Morbius and Mystique, but it can generate a lot of points.

Note that The Collector's effect is not ongoing. That means Mystique can't copy it, but also Enchantress can't nullify it.

Swarm

Swarm is the key to multiplying power. Each time MODOK discards Swarm, I get two Swarms that cost 0. This can generate 2 points through Morbius, 2 points through Mystique, 1 point through The Collector, and 3 points by itself.

With On Reveal doubling effects, it's not hard to double this. Ending up in round 6 with 6 Swarms is not very hard.

Colleen Wing

Colleen Wing is only there to discard Swarm. Doubling Swarm before I play MODOK means more points across the board.

All other typical Discard deck cards (Blade, Lady Sif, Sword Master, etc.) don't give you control over which cards get discarded.

Colleen Wing is predictable, and I have removed low cost cards from my deck to make sure I can use this card to discard Swarm, because that will increase my final score. Nothing is more annoying than having Swarm and Colleen Wing but you have a bunch of other 1 or 2 cost standing in your way.

When I see someone play Swarm, or Colleen Wing and it discards anyone but Swarm, I know they're not using the same strategy I am.

Apocalypse

When Apocalypse gets discarded, it comes back with 4 more power, so when I play MODOK on round 5 with this in my hand, it gives me a ton of power to play in round 6.

Wong

Wong allows me to play MODOK twice, which means twice the points to Morbius, Mystique, and The Collector, twice the Apocalypse power boost, twice the Swarms, and a great synergy with Helicarrier.

Helicarrier

Helicarrier is a powerful card in its own right, but it's particularly nice if I have it in my hand and I can somehow play MODOK twice, because 3 cards will enter my hand (giving me points through The Collector), only to be discarded again (giving me points through Morbius and Mystique). It has great synergy in this deck.

It can hurt to see some powerful card like a Knull arrive in my hand, only to be destroyed again, but that's how this deck works. But if I can only play MODOK once, it doesn't hurt to get some nice cards.

Black Cat

Black Cat is more a support card. Either this card discards itself, feeding Morbius and Mystique. Or I get to play it, and 7 points isn't bad.

Remaining cards

That leaves two places in my deck that I have switched in and out over time.

Moon Girl

I thought Moon Girl might be cool because it draws cards, meaning I get points through The Collector. But it hasn't been that useful so far. Hand size is something I have to carefully manage with this deck - I want a lot of cards, but not so many I can't draw - so doubling my hand can be tricky.

Rocket Raccoon

Rocket Raccoon is there in case I just need a random card for something and want to generate some early points. I used to have Iceman, for more or less the same reason.

I will probably keep fiddling with these two places. I might try some utility cards. Scarlet Witch can be useful, but anything that costs 2 or less might interfere with Colleen Wing and Swarm.

Cards I tried

Here are some other cards I tried in this deck.

Crystal

I thought Crystal might be great to play on round 4 in order to get MODOK on round 5, but it also means I lose my existing cards. When I have Swarm and Apocalypse without MODOK on round 4, Crystal won't help me.

Dracula

It's apparently a common tactic to play Dracula on round 4, then end up with a sole boosted Apocalypse at the end of round 6, but I could never quite make that work reliably.

Adam Warlock

I thought Adam Warlock would be great to help draw cards faster early on, and I occasionally made it work, but because I have no reliable way of building power early on it's hard to play it so it draws cards.

Hela

I had Hela in this deck early on but meh. It just wasn't that useful, because it only works if you draw it on round 6.

How I play this deck

Playing this deck is pretty simple.

In rounds 2 to 4, I try to play Morbius, Mystique, and The Collector, Colleen Wing, and Wong.

Then I play MODOK on round 5, and ideally Apocalypse and 2 to 6 Swarms on round 6.

The dynamic is that I have very few cards or points in the first 4 rounds. Ideally any card I don't play will generate more points in round 5, when it gets discarded, than if I do play it. This means I can sneak up on opponents, and snapping in round 4 or at the start of round 5 can work well.

Here is my most dramatic win with this deck yet.

It's the start of round 6. My opponent as 44 points, I have... 2.

Dramatic Marvel Snap MODOK deck win - start of round 6

Now it's the end of round 6. I have 98 points. That's 96 points from a single card.

Dramatic Marvel Snap MODOK deck win - start of round 6

Note here that I didn't even play Apocalypse or my Swarms: that's what I meant by having multiple ways to deliver a ton of power. I can win without playing cards after MODOK, I can win without playing cards that generate points off of MODOK, or I can win with some combination of both. I personally find that more reliable than having a deck that relies on a rarer combo of cards.

Vulnerabilities

This deck is still plenty vulnerable to other cards and locations. Leech and Enchantress can disable Morbius, Swarm, and other key cards. Rogue can steal Morbius's effect. (Today I had a Rogue attack Morbius just before Mystique was revealed. That hurt.)

This deck is not great when I have few locations you can play on, and it has no way of affecting locations I can't play on, like Death's Domain, Luke's Bar, or Sanctum Sanctorum. But it is great with e.g. The Vault because I can play e.g. Morbius there early, and generate power much later.

When Sokovia was the frequent location, I had a bad time, because it discarded MODOK quite frequently.

Other locations that might make me retreat early: Attilan, Bar With No Name, Dream Dimension, District X, Gamma Lab, Mindscape, TVA, Weirdworld.

I can usually work my way around some of the other ones. Elysium, Bar Sinister, Sinister London, Kamar Taj, and Onslaught's Citadel are huge. Nova Roma can be super helpful too.

Conclusion

And that's my MODOK deck. It's not OP, but it has taken me past season rank 40 to season rank 60 for the first time since I started playing Snap. When it hits, it hits big, and that's super satisfying.

Revamp! 2023 edition

If you're reading this, I finally finished converting my WordPress blog to a statically generated website, built using Nikola, and hosted on Netlify.

I started this whole process in September 2021, worked on it off and on - mostly off - and now it's good enough to turn the switch, and then tinker with it some more.

I am still trying to figure out what blogging means to me these days, but I do want a personal website I control and can update.

WordPress was fine, but I got a bit tired of having to update it and all of its plugins, and I suspect that during some PHP or WordPress or MySQL update by my hosting provider, it ate all of the special characters.

Now I have to update Nikola and Python and remember the workflow... but hey.

A ton of external links are broken. I did a reasonable amount of work to keep incoming links working, but seeing how many external links no longer worked, I lowered my standards. Still, I expect most links from the last version of the WordPress hosted version of this site to still work. On the back end, I had a big redirect list for even older links, but, well.

All comments have been restored - they were also broken on the WordPress version. However, I have not activated any kind of mechanism for anyone to comment here. Link to my post, or use social media, I guess.

Quick update (2016-2021)

I have been much more active on Twitter than here over the last few years, but I never intentionally decided to abandon my blog. So, in attempt to catch up, here is a list of some of the things I've done in the last 5 years, which I've not previously managed here:

I'll be writing more about each of these over the next few weeks.

The reason I haven't blogged as much: moving to Canada was a big life change, working on Legion was intense (not in the crunch sense, it just took a lot of my energy), and working at a big AAA company sadly meant fewer extracurricular activities, especially public ones.

So why am I blogging now? Because I recently quit Ubisoft to work on something exciting. Announcement soon!

Elision in generated interactive stories

I've been thinking about generating interactive stories as choice-based IF for a while, using that format as a liberating constraint, a reasonable and minimal model for many other forms of interactive entertainment, and as a tool to focus on interesting player choices. I wanted to do something for Procjam in 2014, but got overwhelmed by the problem. I was planning to tackle thing again in a similar but smaller project for ProcJam 2017. That is not going to happen in time, for personal reasons, but last night I realized that a) the problem I was stuck on with was the same problem that overwhelmed me back in 2014, and b) it might be possible to solve it by splitting it into two simpler problems. So I thought I'd write down some notes here.

Choice-based IF, with its focus on scenes over systems, allows you to do something that is very rare in other forms of interactive entertainment but common in non-interactive storytelling: arbitrary focusing and eliding of events and elements. In a novel or TV show, being able to focus on some things and hide other things is crucial. I want to show the detective noticing a detail about a possible suspect, I want to hide the long drive she took from the police station to the suspect's house.

This is something that we barely do in most games. The most common forms of focus and elision in games are diegetic - we've arranged space so the boring travel time is not too boring - and systemic - we always skip, or allow the player to skip, certain parts, for instance by always hiding the transition between selecting a mission and actually starting it.

(Whenever I bring this up, people mention 30 Flights Of Loving and Virginia. Focus and elision are bigger than what these games do, but it's interesting that these are the only two examples people come up with, and both are fairly recent games.)

This is a huge subject to me, which I've been struggling to express since 2010, so I'm not going to dwell on this. But I'm convinced we could be creating a broad range of new interactive experiences by doing more intelligent focus and elision.

In choice-based IF we are not bound by the rules and conventions of other game formats. We can slow down or speed up the experience as we see fit. Which is great! Except that when I tried to think about generating a story with interesting choices and good use of elision and focus, I got stuck. And while thinking about my current side project, which involves generating diary entries, I hit the same wall.

The approach that occurred to me last night is to separate the event generation from the rendering into text. This is more appropriate for diary entries than for a playable IF game, and there are reasons why you want the two to be linked, but my hope is that separating the two will allow me to advance with my side project (time and energy permitting) after which I should have a better understanding of the problem.

(I haven't yet read this article on rendering Skyrim in text, but I'd be surprised if it were not related.)

Anime series recommendations

For no particular reason, and without claiming to be an expert at all, I thought I'd list some anime series I like. In no particular order:

Paranoia Agent. Coherent weirdness by the late master director Satoshi Kon.

Cowboy Bebop. Very stylish science fiction noir. Great characters. The movie is great too.

Samurai Champloo. Very stylish samurai action by the director of Cowboy Bebop. Flash forwards into the current day? Beatboxing samurai? Check. Amazing fight scenes.

Ghost In The Shell: Stand Alone Complex (aka GitS:SAC). More like the second movie  Both series are excellent. There is a SAC movie and a new series, all good.

The Melancholy of Haruhi Suzumiya. A story about an annoying high school girl. Saying more, including the genre, would be spoiling things. The episode order has been randomized because why not tie one hand behind your back? Touching, hilarious, exciting, coherent.

Serial Experiments Lain. Twin Peaks meets 90s cyberpunk.

That's it. I've seen other series, but… I didn't like them as much as these ones.

Some game-designer-y thoughts on Bound

I've just played Bound, the game for the PlayStation 4 developed by Plastic and published by Sony. Apart from some images and a brief look at a trailer, I knew nothing about it. If you want to play it, I recommend you try to find out as little as possible too. Seeing the tag line and the briefest of descriptions while finding the previous link gave me information I would have preferred gathering myself.

Here are some reasonably non-spoilery thoughts on it, based on having played it for an hour or so:

Unusual combinations of skill sets can lead to unusual products, and competitive advantage. 

Bound is beautiful, and shows a rare combination and integration of visuals, audio, and programming. I believe Plastic has roots in the demo scene, and it shows. It has demo aesthetics but with a bit more sense and coherence than one usually sees in demos.

The animations seem to have been captured from a trained dancer. They're beautiful and striking.

For how unusual skill combinations lead to competitive advantage, see my Gamesindustry.biz article on innovation

I am not a huge fan of metaphorical stories.

Bound mostly takes place in a very non-naturalistic world, but there are obvious signs that this all symbolizes something in our world. The story is heavily metaphorical, or symbolical, or allegorical. (Let me know if you have a well-founded opinion on the differences between these, and which one Bound would be.)

It's a way to tell stories that I think works well in visual media, and games can obviously be a visual medium. It works because it gives an excuse to visualize concepts that one would otherwise need words to convey. Also, gameplay actions can be a metaphor for more abstract struggles.

Still, it fills me with weariness. Spending hours trying to deciper the story will occupy my brain, but I'm not sure if I enjoy it. At least, since the meaning is outside what I am seeing, and hidden from me, it's "okay" that my actions appear to have little meaning in themselves.


Games like Bound don't fit common models for interactivity and computer games well.

Bound evokes a lot of emotions, but they're not the emotions we typically talk about (to the degree that we do) when we use the typical lens of systems, meaningful choices, resources, etc. Bound is filled with discovery, joy, beauty, and it's far from the only game like it. But if there are meaningful choices I've not seen any yet. That does not in any way mean that I believe Bound should not have been developed, or should not have been marketed as a "game".

Beware of relying on a limited set of tools and theories. Be aware of your focus and specialization. Work on your mental toolbox.

Finally, Bound reminds me of a game I've worked on in the past, but since that game has not been release yet, and I don't know how it has changed in the 3 years since I worked on it, I will keep my comments until later.

Game animation logic in React

I've been working on a small minigame written in JavaScript using React. It's similar to Robo Rally: you write a small program for a robot, it executes it, if it gets to the end without dying, you win. The actual robot logic is turn-based, the display isn't. It's split up like this:

One module contains a pure functional implementation of the actual robot / program execution logic. It exposes two functions: one that takes a level description and returns an initial game state, and one that takes the level description and a game state and produces a new game state.

The next module contains a Flux store (just something that holds data) which holds the level descriptions, the current game state, and can run a timer. If you tell the store to start, it starts the timer, runs the robot logic every tick, and emits an event when its data changes. (It also handles a whole bunch of other state, but that's irrelevant for this description.)

Then there is a React component for the robot (obviously one of many components). React components are, ideally, pure functions of their inputs. They're best written declaratively: you use React to declare what you want the user interface to be like. This usually works like a charm.

But game logic can be hard to fit into this model.

The approach I've taken is to say that the robot logic knows nothing about animations, and neither does the data store. When the robot makes a move, the store emits a change event, and the robot component gets new data from its parent component (a standard approach in React). This includes its position and orientation, but also an 'action'. This action is like an instruction in a stage play. For movements, actions are "move in from the left", say. The robot component then goes and plays an animation on itself for this action. So it goes "OK, I am here, I'm moving in from the left, so at t=0 I am one square to the left, and at t=1 I am over here". Then it runs a timer, and can calculate exactly where it needs to be at any given time.

Another approach could have been to store the robot's current coordinates locally inside the component (in this.state, in React terms), and to compare them to new incoming coordinates, and animate based on that. The problem with that is distinguishing between 'you just made a move' and 'the entire board was just reset'. The action-based approach solves that: I can just send an action called 'none' or something.

So far, so good. A problem occurs when the data store emits a change event that is not tied to a robot logic tick, because that makes the robot go "oh you want me to come in, alright then" and it resets its timer and does the move all over again. And that looks like a bad glitch.

What I expect I need to do is intercept the incoming data (in the shouldComponentUpdate part of the React life cycle), see that its the same as what I had already, and only start a new animation when the data has changed.

This way I can distinguish 'a tick happened and you should move' from 'something changed so let's re-render everything because *shrug* React'. (The main selling point of virtual DOM libraries like React is that they can make re-rendering everything very fast.)

Does that make sense? Are there problems or solutions I'm not seeing? Is this a terrible way of going about things? I have a slightly bad feeling about inferring events from data changes. But as far as I can tell this would be the React way, and I don't see a better way that would permit the same level of encapsulation.

Announcing Choba, an experimental interactive storytelling engine

Over the last few weeks I have taken the interactive fiction engine inside Mainframe, the IF game Liz England and I made for Procjam last year, and have rewritten it in JavaScript. I've called it Choba, short for CHOice BAsed, and I've put it on GitHub and npm. It's open source, just like Mainframe.

I've also backported it into Mainframe itself, so if you play it now, you're no longer seeing a Python program running on Heroku, but a JavaScript program running inside your browser. (Doing just that has already been worth it. It means I no longer have to pay Heroku, it makes the game much easier to deploy, and I've already learned a lot starting a new JavaScript project.)

As you can see from the project's readme file, I have future plans for this engine. Writing an actual parser, porting it to desktop and mobile, experimenting with different game types, better tools, and new procedural generation / narrative AI techniques: these are all things that have come a step closer.

I don't recommend it for general use yet, but I do welcome feedback, and if you want to build a game with it, let me know how I can help.

More thoughts on tagging

I did some more thinking after yesterday's blog post on tagging, and had an interesting discussion with Mike Cook and Chris Martens about how they approach similar problems. So here are some more thoughts on the subject.

Namespaces might be interesting for tags. Right now tags are global, and that means a tag defined in one place for one context could interfere with content somewhere else. I've already briefly hit situations where I had to rename tags to avoid that. This is another thing that works in small projects but will cause trouble when you scale up. A very similar problem exists with CSS in large scale web projects, and CSS is similar to tagging.

If instead of "give me something with tags 'corridor' and 'spooky'" you could say "give me something from the namespace 'spaceship' with tags 'corridor' and 'spooky'", you can avoid getting your spooky castle corridors into your spaceship. Setting a namespace could be done both explicitly ("this scene has the 'spaceship' namespace") as well as implicitly ("everything read from the castle folder has the 'castle' namespace"). I kind of did this with the Diablo-like I mentioned by having an implicit setting tag, although that's not quite the same as a namespace, perhaps. Or is it?

Is a namespace like a meta-tag? Is a namespace like a normal tag? Could the desired namespace be a variable? Could you have a hierarchy of namespaces? Is that the same as just having a bunch of tags? Does madness lie this way? Perhaps.

Tag types could also be interesting. Instead of saying 'castle' or 'spaceship', you could say 'location:castle'. That's already possible now, because 'location:castle' is a valid tag. So by imposing a scheme on yourself you can make your life easier and avoid the problems of tags being global. (This is a solution that people use for in CSS, e.g. BEM, a naming scheme for HTML elements.)

The difference between a naming convention and something stricter is that if the computer understands things like tag types or namespaces, it can give you better feedback and do better analysis. Putting knowledge into the computer is almost always a good idea.

Take it one step further and you could say "location = castle". And then you have a conditional expression, which can query any state variable. And, with a bit of work, any attribute of any entity, object, location in your world model. Mainframe allows you to use 'PC_job' to refer to the job title of the player character (which is randomly picked from a list, based on the story act). But a nicer version is to say 'PC.job', because then you can easily extend it to other attributes.

So then you could say "give me a scene that matches the current setting and mood," and the engine will find scenes with setting = castle and mood = spooky, say. Or you could just say "give me a scene", and it's the scene that knows which attributes it applies to. Is that confusing, not being able to explicitly specify what you want when you want it? Perhaps, but with good tools that shouldn't be a problem, and you'd need that anyway to scale this up. There's also the question of loss of flexibility when you move from a super-simple tag system to something more complex.

In any case, continue along this route and perhaps you'll approach what Mike and Chris are doing, which is to use predicates and formal logic. I could understand the example Mike gave on Twitter from one of his jam projects. Chris has taken this to dizzying heights: her work on Ceptre makes me regret having dropped out of computer science.

Conclusion: I don't know yet. The thing I want to do now that I have some more results from working with tags is to look at it from multiple points of view and look for "if you look at this as [rules/variable names/logic] then things become simpler and/or more powerful" insights. This is why I'm interested in programming languages all of a sudden. More on that in a future blog post.

Procedural content generation in Mainframe

The procedural content generation in Mainframe uses a very simple mechanism, which is both more powerful and trickier to implement and use than I expected at the outset. That mechanism is tagging. You tag bits of content, and then somewhere else you say you want something with a given set of tags.

One of my theses about interactive storytelling is that selecting, adapting, and combining bits of authored content is an approach that is powerful, underexplored, and pragmatic, in that it offers a smooth learning curve from simple and known to, I hope, complex and new. Mainframe is, among other things, an experiment with this approach.

Tagging is one of the more interesting ways to select content. I first saw it used in 2008 as the interface between the AI and the audio system in LMNO. Back then I was mostly impressed by how it reduced the production dependency between AI and audio.

In 2010 and 2011, I worked on an unreleased Diablo-like that used tagging to procedurally generate levels. I did a lot of work on the level design and tool chain. At GDC in 2012, I saw Elan Ruskin's talk about the dynamic dialog system used at Valve, which used an advanced tagging approach to allow writers to create dialogues. In 2012, we used tagging to select texts in a mobile game. I remember vividly how the actual tagging logic consisted of one line of code, but it took three of us a day to write that line. (It was a LINQ expression in C#, if you're curious.)

The system in Mainframe is really simple. The core logic is this function:

def tags_are_matched(_desired_tags, _available_tags):
    for desired_tag in _desired_tags:
        if desired_tag not in _available_tags:
            return False
    return True

(Python nerds: I know this can be written in one line.)

All it does is check for a given thing whether that thing has all the tags we want. Very simple. For the Diablo-like, we added a "nice to have" qualifier, and I really wanted a "NOT this tag" qualifier. But in Mainframe this was perfectly sufficient.

Liz could write something like:

<injectOption tags='option, containers' />

and the engine will look through all of the scenes for one with the tags 'option' and 'containers', and will then inject a link leading to that scene into the current scene.

A nice little feature in Mainframe which made a big difference is that a desired tag can be a reference to a variable, as well as just a literal tag. So this line:

<injectOption tags="computer_talk, $flesh_act" />

makes the engine look for a scene that has the tags 'computer_talk' and whatever the current value of the 'flesh_act' variable is. (To understand why it's called 'flesh_act' you have to play the game...) This allowed us to change the game depending on the act of the main storyline the player is in.

Instead of scenes the engine can also inject 'blocks'. Liz mainly used this to inject flavor text depending on game state, but we also used it to factor out common logic, like this:

<!-- Used to init variables when the player respawns. -->
<block tags="pc_init">
<!-- reset all values other than main story act & total data -->
<action act="set $has_mcguffin 0" />
<action act="set $is_fed 0" />
<action act="gen_data" />
<action act="set $sacrifice 0" /> <!-- player has not sacrificed body parts for data -->
<action act="set $injury none" /> <!-- player's current injury, in case we do others -->
<action act="set $commands 0" /> <!-- used for PC to use computer 3x before needing more data -->
</block>

You can see the 'action' element there, used to modify the game state.

Despite me having used tagging before, I still learned a couple of interesting things during the development of Mainframe.

The subtleties of picking

"Pick a scene with the right tags" sounds easy, but there are a lot of subtleties. We didn't want the content to be picked in the order it was defined in, nor did we want every player to see the same content order every time. So we needed randomness.

Our requirements for random picking, combined with the fact that Mainframe is a web-based game without a database, made for a ton of subtle errors. I spent more time debugging and rewriting that part than anything else, and it required cryptographic techniques plus me actually cracking open Knuth's Art of Computer Programming, probably for the first time in my life. I have a draft for a blog post on that lying around, so I won't go into great detail here.

I wrote a class called TaggedCollection, which contains a set of tagged items, be they scenes, blocks, names for the data you find in the game, or whatever. When the get_item_by_tags() method is called, I create a list of items that have the desired tags, then I shuffle it, and pick the next item. I can't store that list (long story), so I regenerate it every time. (It's a game jam, who cares about performance, the lists are very short.) I store a random seed and an index per desired tag set, per player.

Shuffling is OK but not great. The chances of getting the same item twice in a row are low but not zero. If you have three injectOptions in a scene, like we do, you can sometimes see the same item twice, because the list is exhausted, reshuffled, and an item at the end is now at the start. The solution I am currently planning to implement for this is to turn those three injectOptions into a dedicated command, at a higher level of abstraction. This would also give us some other advantages, like being able to control death (some scenes kill the player).

The most intriguing effect of this approach is that rendering the scene modifies state, because of those indices that get increased.

(So what happens when the player reloads the game in their browser? Fun. Fun is what happens.)

I don't know any other game engine that does that: you always want to separate rendering from updating, and in general you want to keep a firm grip on your mutable state. My day job has made me dig deep into Facebook's Flux pattern and immutable data structures, so I am very conscious of state mutation these days.

This is really the most interesting thing I learned about tagging, and I haven't yet decided what I want to do about it, if anything.

Tagging as a programming language feature

Internally - I intend to describe this in more detail in a future blog post - the engine data structures resemble an Abstract Syntax Tree or AST. So it is possible to imagine all the data as a program that produces an interactive fiction game, and the engine is the runtime for that program.

Now, if you follow that train of thought, blocks and scenes become like procedures, because they can contain logic, including logic that modifies game state. Tagging then leads to a programming model where adding or removing a procedure can indirectly affect the behavior of the entire program. If you look at the data as a program that is very strange!

Tagging in production

This was something I had already encountered in the 2011 Diablo-like, and back then I wrote a pretty elaborate tool that read in all the content and analyzed it to make sure all tag demands could be fulfilled. It emulated the server level generation algorithm to make sure we could never break the server through bad tagging. I hooked it into the continuous integration server, because I take it as a personal insult when a bug occurs that is hard to find for a human, but easy for a computer.

What all of this means, apart from that I have minor OCD, is that you need special tools to guarantee correctness when you use tagging. It's not witchcraft, but it does take some effort. I would want to expand that to show not just unfulfilled demands, but also unused content and places that are overly sparse or dense.

Another big issue with this system, and with procedural content in general, is test coverage and state manipulation during development. There is a bunch of stuff in Mainframe that I added but have never been able to really test, because of the random element and the lack of any functionality to directly pick content and affect state. The game is simple enough that I don't think there are any real errors, and we ran spellcheckers over the raw text, but this is a weakness that would cause trouble when scaling to bigger games, and properly implementing this testing and development support functionality is not trivial.

Tagging as story generation

The way we pick tagged content in Mainframe is just one of many. It can be interesting to pick scenes in order rather than shuffled. It can be interesting to stop picking scenes once they've all been shown. It depends on the content. Lots of patterns are possible.

There is an element that can be found in many board games that I like a lot: the event deck. They are simply decks of cards, shuffled at the start of a game, and under certain conditions the top card gets revealed and whatever is on there "happens": players get items, monsters get introduced, stuff blows up, etc. There are lots of variations, and many games have multiple decks.

Event decks are story engines. They represent the progress of time, external events that keep happening, mounting tension, advancing plots. By being shuffled they introduce a random element, and through their design or through other rules, they give the designer some control over the experience.

Tagging is like an event deck. In Mainframe, whenever the player chooses to go on a mission for data, the top three cards from the event deck are revealed (links to the next three scenes tagged "mission" are injected). Because this tagging is done based on the current act, there are effectively five decks. Some scenes are tagged with multiple acts, so they appear in multiple decks. It is very simple conceptually, but quite powerful.

Because the tag can come from a dynamic variable, it is possible to imagine more complex decks. It is also possible, with the higher level injection command I described above, to generally use more complex logic. Because really what is happening is that we look at the player input, at the current state (representing what the player has done), and at the content we have available, and we pick the most appropriate thing to show next. That logic is core to interactivity (it's part of Chris Crawford's definition of interactivity), and, in a game like Mainframe, it heavily involves storytelling logic. We want certain things to come up sooner, others later. We want things to come to the fore or recede to the background, based on what the player has done. We want the odds of certain things to increase based on state - for instance, in Mainframe, it would have been nice to control the odds of the player dying, or the odds of the player encountering... certain things.

All of that starts with a five line function, something that can be understood by analogy with a deck of cards. That is why I get so excited by content selection algorithms, tagging, and event decks, and I hope to dig deeper into them.