Official development blog

Turn Time Systems

I’ve always enjoyed turn-based games, giving you unlimited time to consider and react to changing situations (even if you don’t always take advantage of that opportunity and instead bumble into mistakes far faster than necessary). Turn-based mechanics are especially apt in traditional roguelikes, where the RNG typically plays a starring role and therefore said changing situations can be unpredictable, but exactly what can be accomplished in the space of one turn, and what a turn really represents, varies from game to game.

At the most basic level, we have turn mechanics which are simply [player acts] -> [enemies act], all in sequence, one action per turn, and then the queue starts over again, [player acts] -> [enemies act], and so on. Then some games might add longer actions, though still very clearly based on “whole turns,” like performing a longer action forces the player to skip their next turn. Or the more complex variety: An action requires first waiting for two consecutive turns without interruption (by attacks, for example) in order to be carried out.

But sometimes even more granularity is desirable for content or balance purposes. Like what about actions that take one-third of a turn? Or 2.5 turns? Roguelikes of significant scope are more likely to need a greater level of granularity in order to accommodate more content in a balanced manner. (It’s not absolutely required, of course, but it’s helpful in the long run :D)

This granularity can be a double edged sword, though, since there’s always the chance that as a time system grows increasingly nuanced, the more oddities arise and the more opaque or confusing (or at least challenging) it might become, both to build around and for players to master. As with any game mechanic the opaqueness is at least generally solvable through a better interface and feedback, if necessary, but it’s worth noting that systems with higher granularity will also be much less predictable, so hopefully precise turn predictability isn’t an important factor in a roguelike that also features extreme granularity!

Cogmind Time, 2012~2018

Cogmind 7DRL, written in 2012, was my second roguelike project, based mostly on the X@COM source code. Well, X@COM being a reimagining of the original UFO Defense, it was a squad-based game with explicit Time Units which could be used to perform actions by each soldier until declaring the end of the turn and allowing enemies to act. There was no single player character, and most turns by each individual involved a sequence of multiple actions limited by their stats and what kinds of actions they wanted to perform.

Migrating from this sort of system to a decidedly more roguelike time system would be a pretty big leap, and being a 7DRL I wasn’t going to have a ton of time to experiment with what does and doesn’t work, so I searched around and decided to adopt the system demonstrated in this useful Rogue Basin article.

What is a “roguelike” time system? I think the most basic assumption is that it consists of a single actor performing a single action at a time, and after their action it’s at least possible, if not likely, that other actors will get to perform their own action. Of course there are many potential variations, and definitions don’t mean much in the bigger picture, but X-Com’s TU-based turns would not be considered very roguelike, which generally boil down to [one command] -> [one action] -> [implicitly done].

I picked the system in the linked article because it seemed easy enough, and at the time I wasn’t really familiar with how roguelike time works under the hood anyway. The system is not a simple queue, and has some interesting characteristics, but I’ll get into those details later when I cover how they eventually became a problem and I recently decided to replace it!

For the next bit of discussion, all you need to know is that each “turn” is subdivided into 100 “time units.” Actors can carry out actions that might require only a fraction of a turn (< 100 units) or multiple turns (e.g. >= 200 units). While sometimes more difficult for players to wrap their heads around at first, and also sometimes difficult to accurately predict even once it’s understood, I believe its flexibility outweighs those potential negatives.

Action Costs

The length of an action is extremely variable in Cogmind. Most actions require a static amount of time, but the two most common actions, moving and attacking, are calculated based on multiple factors, and therefore change throughout the game depending on your status.

To keep things simple, the majority of actions take either one turn, half a turn, or 1.5 turns:

Cost Action
100 Pick up Item
100 Attach Item
150 Attach from Ground
50 Detach Item
50 Drop Item
150 Swap Item (Inventory <-> Equipment)
100 Misc. Actions (Ram / Rewire / Escape Stasis…)

And that’s it--Cogmind doesn’t actually have a wide variety of unique action types, and for simplicity sake a lot of miscellaneous actions require precisely one turn. But with one turn the equivalent of 100 time units, there’s a lot of leeway for fine-grained requirements when it comes to the most common actions: moving and attacking.

Moving even a single space involves a potentially huge range of time, quite different from the average roguelike. How long it takes to move is highly dependent on the form of propulsion:

Cost Propulsion
40 Flight
60 Hover
80 Wheels
120 Legs
160 Treads

Those are simply base costs, though, which might vary somewhat with unique items, and which in the case of flight and hover can be further modified by using multiple items at once. For example using three flight units will be faster than using two.

So in a simple scenario, a flying actor (robot) can move three times for every one move of a legged robot, or four times compared to a treaded robot. And that’s only given the base costs! Assuming a loadout of five flight units, each of which gives a -3 cost modifier after the first, the movement cost is 40 + [-3 x 4] = 28, or 3.57 moves per turn, or the equivalent of 5.7 moves for each treaded move. For Cogmind’s first several years the additional flight unit cost modifier was -5, but for balance purposes this was adjusted down in 2016 as it was a little too easy to reach very high speeds.

The highest speed currently possible is 20 moves per turn, though that speed is much more difficult to reach than it was in the early days. Not to downplay the effects of even “average” fast speeds once compounding is taken into account--a meager three-times speed advantage means that for 10 moves by a pursuer you’ve traveled 30 spaces, which is usually plenty to reach safety.

In the opposite direction, movement is slowed if overweight (an effect that matters more for the normally faster forms of movement), so it’s quite difficult to escape a swarm of flying robots tailing you if you’re a mass of components hopping around on one leg.

As you can see, flee/chase situations can play out very differently depending on the relative speeds of those involved, but it doesn’t lead to boring play; instead it’s possible due to the world being an active ecosystem spread across huge maps rather than than based on individuals or groups of monsters waiting within a small area. Escaping danger in one area can still lead straight into danger from another.

Overall, this approach to movement leads to interesting scenarios, like being stuck in an overwhelming firefight without any propulsion (or weak propulsion) and forced to drop things and run in order to survive, or flying so fast that almost nothing can catch you (so long as your sensors help keep you from running into more trouble ahead), or so fast they can’t even see you (since robots only register targets during their own turn, by which time you could be long gone!).

Because movement speed is an important factor in turn-to-turn play, it is displayed on the HUD at all times, though in one of two forms. For beginners it’s shown as a percent of base speed, so 100% when one move = one turn, 140% when 1.4 moves = one turn, etc. This is to keep it more intuitive at first, rather than having new players misunderstand that in terms of time units, technically lower “speed” numbers are faster. Players who activate the more advanced “Tactical HUD” mode in the options see instead the actual current movement cost itself (thus the meaning is reversed, higher is slower).

cogmind_speed_display_cost_mode

Speed display, percent mode

 

cogmind_speed_display_percent_mode

Speed display, cost mode

 

cogmind_speed_display_slow_warning

Warning: Dangerously slow. When beginning a new sequence of movement while quite slow (one-third normal move speed or slower), confirmation is required in case the player simply forgot to activate/attach a propulsion unit, because the consequences can devastating if you take one slow move while under fire!

Attacking has smaller time cost scaling than movement, but is interesting in that its costs are greater than those of other actions, especially movement. The base cost to fire a single weapon is 200 (two turns!), meaning defenders can more easily escape after coming under attack (if they want to). This effect is even more apparent once the time cost of an entire “volley” is taken into account. Weapons are often fired in groups (called volleys), and the total cost of firing the entire group is applied at once (front-loaded, like all action costs in Cogmind):

Cost # Weapons
200 1
300 2
325 3
350 4
375 5
400 6+

In the earliest versions of Cogmind, firing costs for multi-weapon volleys increased about twice as quickly as they do now, but the rate was later reduced to encourage builds with more weapon slots, since they already have other issues to contend with such as the resource costs involved in larger attacks.

Most robots have two weapons, so if you’re relatively fast, each time enemies fire you can fly something like 10 spaces. Even average-speed robots can move three spaces in that time, important for repositioning to a more defensible location. And for combat-focused players, the system is obviously designed to give them an advantage in one-on-one combat, since few actors other than the player are capable of using that many weapons at once.

Sometimes individual weapons may modify the required time (firing speed), though they’re the exception rather than the rule. More unique among this system are melee weapons, which can only be used one at a time (multi-wielding is possible, but additional weapons only have a chance to follow up a primary attack). They are almost always slower than projectile weapons (~300 time/attack), but also more damaging, and come with their own special effects.

Before firing, the HUD displays how much time the currently activated weapons collectively require to fire:

cogmind_volley_firing_time_demo

Volley firing costs indicator, changing as weapons are toggled (Autoguns have a faster firing time).

I think Cogmind is a good example of a unique approach to time management, showing that even while using a very traditional system roguelikes don’t have to follow the principle that the cost of one action is near or equal to a single turn, or a multiple of turns.

New Challenges

Of course it’s only a matter of time before extreme min-maxers come along and pick apart all the system details in a roguelike to scour it for every last possible advantage. They got around to the time system in 2017, and discovered that it was possible to game subturn actions such as quick movement to figure out when it would be impossible for AIs to mark the player as a target, thereby guaranteeing free peeks around corners without being spotted.

Only a few players knew about this, and even fewer actually used it, but as an “optimal but tedious” strategy (and also an unintended one) this was definitely something to eventually put an end to. Besides, the original time system might not even be the most appropriate for Cogmind--heck, I barely understood its current gameplay implications and had just brought it on board for a quick 7DRL years ago!

At first, mainly because it wasn’t having a broader impact on play, I just wanted to try some quick band-aids to see if I could fix this under the existing system without digging too deep.

So I slapped on two separate “fixes” that year, specifically: 1) randomizing turn offsets for each AI (so they didn’t always start their life at 0 time, and therefore be likely to act on turn boundaries, especially if stationary like guards), and 2) changing the “wait” action to cost 100 time units, rather than simply draining the actor/player of all their remaining time in the current turn.

And neither of these worked because, well, I admit I didn’t understand the time system I’d borrowed seven years before, and hadn’t actually read through to understand what was really going on xD

At this point I should explain the actual mechanics of that time system! Basically:

  • Each actor gets a certain amount of time units to spend on actions during their turn, for example a base amount of 100 time units, which might be modified by stats or status effects.
  • During their turn they perform actions that consume this pool of time, and as soon as their remaining turn time reaches zero (or even goes negative due to a time-intensive action), their turn is over, and they are moved back into the queue of actors where they belong, depending on their current time value.
  • Each time they reach the front of the queue, they again have a new pool of time units to use, again based on their stats or status effects.

This system has some interesting characteristics due to the fact that in some cases it can allow for an actor to make multiple uninterruptible actions in a row, assuming the actions cost less time than their available pool. Really you might notice that’s a lot like the X-Com TU system, only handled at an individual actor level (plus you can overspend your available time) and therefore feels much more roguelike.

Such a system also allows for different actors to have different amounts of time available to them per turn, essentially speeding up or slowing down all of their actions relative to other actors. Now in Cogmind I actually never used this adjustable time pool feature at all! Funny enough, I do distinctly remember back in 2012 liking that the system offered such a possibility. There was a spot for it in the actor turn code, but every actor always got exactly 100 time for their pool. Apparently this just wasn’t how I wanted to handle the 7DRL actor “speeds,” not to mention Cogmind’s special movement and attack time mechanics already introduced a lot of variability without the unnecessary complexity that another layer on top of that would bring. Actors could already affect the cost of their individual actions, such as using utilities to fire faster, or attacking with weapons that were simply faster on their own, or speccing for fast movement, or any number of other actor-controlled variables--this level of nuance seems more appropriate for robots built of components.

Anyway, the whole “player always gets at least one full turn” aspect made it impossible to solve the peeking issue--the time system simply wasn’t compatible with Cogmind’s “enemies cannot spot you if it’s not their turn” mechanic. It’s a good system, just not right for Cogmind :P

At this point I still didn’t go as far as replacing the time system though, because there was one other relevant mechanic I’d thought up in the meantime: “off-turn spotting.”

This tackles the issue from a different angle, allowing actors to maybe know about something passing through their line of sight, even if it happens extremely quickly, where the chance for them to notice depends on their type, e.g. Sentries should be more likely to investigate a “potential anomaly.” While so-called “partial spotting” won’t lead to an aggressive chase, they’ll at least check out the area where they think they saw something before going back to their original task.

I like how this mechanic turned out, but honestly it’s tangential to the whole peeking thing.

New System Time

Seeing as I wasn’t even using the original time system as intended, the obvious answer for this problem is to just allow “turns” to be interrupted--immediately reorder the actor queue after every action. Duh. I finally made this change in Beta 8.1, ripping out the old 7DRL system and replacing it with a very simple standard queue.

This change has a number of implications:

  • It’s impossible to be completely sure one won’t be immediately spotted when rounding a corner
  • The chance of being spotted immediately becomes essentially directly proportional to movement speed
  • Common sequences of actions that cost less than one turn, like dropping a couple items, can now technically be interrupted by other actors
  • Other fast-moving actors won’t appear to move so fast compared to a fast player, since moves will be spread out and not lumped together in a sequence

Honestly I’m pretty sure the differences will barely even be noticeable for most players, if at all, but it was about time to remove this unintended, basically non-designed, part of Cogmind. Revisiting it also had another nice side effect which I’ll get to, but first… the bugs.

Oh my, the bugs. I’m pretty bad at technical stuff, so I needed some extra debugging features before I could track them all down.

I already had the first debug visualization from back when I was messing with turn offsets ineffectually trying to solve the problem the first time. (Seriously, if you truly want to solve a problem, examine the entire circumstances first or you’re probably just wasting your time like I was xD)

cogmind_debugging_new_time_system_entity_values

Visualization: Current relative time values for every actor on the map.

This wasn’t enough, though, since under the shiny new system some actors were acting out of order. I originally tried just debugging it internally by looking at values and queue contents, but this was a slow process and before long I had a fully visible turn queue.

cogmind_debugging_new_time_system_entity_queue

Visualization: Current turn queue, soonest to last to act.

The visible turn queue not only shades actors by the relative amount of time until their next turn, but also shows in yellow any that are acting out of order (or somehow managed to get a positive value :/), which had thankfully been fixed by the time I took the above screenshot.

Once all the bugs were ironed out, I also used this opportunity to actually add binary searching to the turn queue. The original time system just uses a linear search, which is fine if you only have maybe 20 actors on a map, but Cogmind has literally hundreds! I did a bit of quick profiling and found that on a mid-game Cogmind map (Factory) 11.59% of the turn processing was taken up by putting actors where they belong in the queue.

cogmind_debugging_new_time_system_optimization_before

Profiling Cogmind’s turn queue updating under the old linear search.

That seemed like a lot, and switching to a binary search brought it down to 2.25% :)

cogmind_debugging_new_time_system_optimization_after

Profiling Cogmind’s turn queue updating under the new binary search.

 

cogmind_debugging_new_time_system_scrapyard

The final time system and visualized turn queue in action in Cogmind’s opening Scrapyard.

 

Turn Queue Explained

Since I’ve gotten questions before, I should probably go into slightly more technical detail about how my basic roguelike turn queue works, especially since it seems so amorphous with regard to absolute time. Where does the idea of a “turn” even fit in here?

A specific example will really help:

  1. Say you have three events in your queue: Player [0], Enemy [0], and Turn [100]. The initiative is set in that order.
  2. Player goes first, spends 120 time on their action, and the new queue order is Enemy [0], Turn [100], Player [120].
  3. The first event in the queue is always the next one to take place, so Enemy goes next, and decides to perform an action that requires 50 time. The new queue order is Enemy [50], Turn [100], Player [120].
  4. So now the enemy gets to act again because they are still at the front of the queue. This time they do something that requires 100 time. The new queue is now Turn [100], Player [120], Enemy [150].
  5. At the front of the queue is… the Turn counter itself! So it handles any absolute turn updates, i.e. things that should happen “once per turn.” Then because each turn is set to be 100 time, the new queue is Player [120], Enemy [150], Turn [200].
  6. So the player acts next, and so on… As you can see, the turn itself is an event/actor, just like the others. You can even add other types of events into the queue if you like, for example as of Beta 8 Cogmind has autonomous weapons that take their own actions independent of the turn counter or even their owner.

Action costs are repeatedly added to each actor and everyone’s time goes increasingly positive as they take actions.  Technically if you have a lot of persistent actors over a very long period, you’ll want to consider eventually resetting the values by subtracting from every event the [time] value of the first event in the queue.

Inserting a new actor is as easy as adding them to the front of the queue and matching their current time value with that of the actor already at the front (or insert them elsewhere as appropriate if it’s desirable to delay their first action).

This entry was posted in Mechanics and tagged , , . Bookmark the permalink. Trackbacks are closed, but you can post a comment.

8 Comments

  1. Edan Deno
    Posted February 12, 2020 at 8:17 am | Permalink

    Dear Kyzrati,

    I am currently developing a rogue-like right now and was having trouble understanding time-systems. After reading through your time-system blog I had a question about it. Specifically the part was the example you put at the end of the post. Using your example where you have an event queue with events player[0], enemy[0], and turn[100]. After an event happens the time it took to do the event is subtracted from the event and the queue is fixed accordingly. So do the time value for each event just get more and more negative or is it reset to 0 at some point? I can see in some gifs of CogMind that they are reset to 0 at some point but I can’t figure out when. I’m sorry if this doesn’t really make sense but I’m trying to wrap my head around this whole thing.

    Thanks for reading
    -Edan Deno

    • Kyzrati
      Posted February 12, 2020 at 8:47 am | Permalink

      Hi Edan! That’s actually a really good question, sorry for the confusion. In fact, the example steps are correct as indicated, and I am actually using positive values rather than negative now! Negative were easier for me to wrap my head around to begin with, but I later discovered that using STL’s binary sorting algorithm would only work with a set of positive numbers, so I ended up reversing the system to work as described in that list. (I probably got it mixed up myself since I’d switched in the middle of working on this modified system, and I think that last section alone was originally written on a different site (r/RoguelikeDev) in response to someone’s question, and when it came time to write this article I then just tacked it on here without considering that the negative part had since changed xD)

      I’ve removed that footnote from the article since it no longer applies. (Updated it with info from this comment.)

      So the numbers actually become increasingly positive over time, and with regard to “resetting,” no it’s never reset. Technically you’d want to do this if you have a lot of persistent actors over a very long time, though, in which case it’s pretty easy: just subtract from everyone the [time] value of whatever’s first in the queue at the time, and you’re zeroed again :)

      As for the gifs above, the debugging info I use shows negative numbers purely for display reasons, with values relative to Cogmind, because that feels more natural to me.

      • Edan
        Posted February 13, 2020 at 10:59 am | Permalink

        Hey Kyzrati, thanks for the quick reply it really helped me understand these time-systems.

        • Kyzrati
          Posted February 13, 2020 at 11:29 am | Permalink

          Excellent, good luck on your journey :)

      • Sartoris
        Posted February 11, 2024 at 9:57 pm | Permalink

        Hi, Kyzrati! Thank you for this post! I have one question about the turn queue. To use your own example, let’s say the order of events takes the following course of action:

        (1) We have the events Player [0], Enemy [0], Turn [100].
        (2) Player acts and now the order is: Enemy [0], Player [50], Turn [100].
        (3) Enemy acts next, uses an action that costs just as much as the Player’s, and now the order is: Enemy [50], Player [50], Turn [100].

        Since the order of events in (3) remains the same as in (2), does this mean that Enemy gets to act again, before the Player?

        • Kyzrati
          Posted February 15, 2024 at 3:40 pm | Permalink

          Hi Sartoris, good question, and the answer essentially depends on your own mechanical/rules preference, like what you think is fair given your game systems and types of enemies and encounters etc. In my case the enemy would then be pushed behind any others with identical times, since that seems more fair to me, plus depending on how frequently that can happen, back-to-back actions are generally more likely to cause a lot of chaos ;)

          (and sorry for the late reply--WP doesn’t notify me about comments anymore, I have to notice there’s something new myself whenever xD — so annoying, I would’ve otherwise replied to you that day…)

          • Sartoris
            Posted February 18, 2024 at 7:12 am | Permalink

            Thank you for the reply, and no worries, I understand.

            I think your solution is quite elegant, and indeed I assumed something like that (pushing the actor behind others) was happening because it seems more fair.

            Love the game, by the way!

          • Kyzrati
            Posted February 18, 2024 at 7:42 am | Permalink

            Thanks! And yeah it took a few iterations to get to this point, but overall this turn management solution has been pretty robust.

Post a Comment

Your email is never published nor shared. Only the anti-spam entry is required. See here for the privacy policy.

You may use these HTML tags and attributes <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>