One Step Build

I just now made build 365, which seemed as good a number as any to mention.

One way I’ve improved the development process is to have an easy way to post a copy of the game for the rest of the team to test. With King of Dragon Pass, this was basically a manual step. Now, there’s a script that increments the build number and does a clean build, then copies it to a web site. It also uses rsync to copy any script files to a Dropbox folder, so QA has easy access to the corresponding source.

This isn’t build automation (it’s not automatic, like Travis or buddybuild), but it’s painless. The basic build takes about 40 seconds (uploading is more variable). And it does mean we pass one of the steps in the Joel Test. Our builds aren’t daily (since we’ve been at this more than a year), but they are at least frequent.

Proofreading

Like King of Dragon Pass before it, Six Ages will have a lot of text. Which means a lot of opportunity for typos or other misspellings.

Most of the text is in OSL scripts, such as these excerpts:

saga: <expeditionLeader> was attacked, but returned home with <his/her> escort.
sagaText: The worshipers suffered the same fate.
text: We eventually pieced this together from stories told by wandering traders.

The scene compiler outputs all strings into a single text file. It looks something like this:

<expeditionLeader> was attacked, but returned home with <his/her> escort.
<expeditionLeader> was attacked and wounded, but returned home with <his/her> escort.
Unfortunately, <he/she> lost the livestock they were driving home.
<expeditionLeader> and <his/her> escort <disappeared mysteriously/were ambushed by trolls and completely devoured>.
The worshipers suffered the same fate.
We eventually pieced this together from stories told by wandering traders.

And that file can be spellchecked. I just use TextEdit. The biggest issue with proofreading is that the game uses a lot of proper names and jargon specific to Glorantha. Luckily it’s easy enough to add “Orlanth” to the dictionary (or the ignore list). More problematic is that variable names (like expeditionLeader) also show up here, though ignoring them usually works too.

Another issue is that it’s a big file. It may be generated by our tool but it still takes a human a while to review, so that doesn’t happen often (in fact the first complete review was today).

This is just a brute force pass. Many typos end up with words that are spelled correctly. And once in a while game-specific names get misspelled. So QA still needs to keep an eye open for problems.

Embarrassment of Riches

I’ve written before about art thumbnails, the initial sketch for a scene that we use to make sure the layout and storytelling work.

three alternate thumbnail sketchesThis set seems particularly hard to choose between. They work in a purely functional sense (i.e. they can be covered with text and still read well). But each has some nice elements. For example, alternative 1 has a closeup, which is nice. But alternative 2 is good for showing the extended family. And I particularly like the children in all three.

Emailing Debug Info

Six Ages tries to make it easy for the player to send us debug info.

Previously, we saw that the game keeps a pair of debug logs on the player’s device. Back in the late 1990s when were did this for King of Dragon Pass, we asked players to navigate to those files (with Finder or Windows Explorer), and send them.

When I adapted King of Dragon Pass for iOS, I had to use a different approach (since the files aren’t accessible). Luckily, iOS had an easy way to send an email message, and we were using Fogbugz to do bug tracking. So the game let the player mail in a report (to the special Fogbugz address), automatically attaching the log, as well as the most recent saved game.

In Six Ages, I’ve improved that slightly, compressing the files into a ZIP archive, and including a few other debug files that help with tuning.

I’m also detecting certain kinds of crashes, and offering to email the report. (In this case, I take a screen shot and include that in the ZIP file.)

Since some crashes prevent this, I also try to detect a crash on the next launch, and offer to send the report then.

This is all fairly trivial code (I’m not using one of the third party libraries that no doubt do a much better job with edge cases), but it makes sending the debug info much simpler for the player. Which makes fixing the bug much easier for me.

Debugging: The Log

Like King of Dragon Pass, Six Ages logs all scripts, which helps show exactly what happened and the state of the game.

Right now a lot of my time is spent fixing bugs in the code. Given that the game is large and complex, it can be hard to know just what happened that led up to a bug. So we keep track of everything important.

This is actually something that we did in King of Dragon Pass. Shawn Steele was implementing our scripting language, OSL, and wanted a way to check that things like conditionals and calculations worked. So he came up with a way to log this to a file. You can see his focus from some of the options:

kAllBranches = 0x01,
 kListSizeing = 0x02,
 kTraceOSL = 0x04,
 kTraceMath = 0x08, // Extra COSL::StartMath debugging information
 kSetVariables = 0x10,
 kTraceFixed = 0x20, // Extra CFixed debugging information
 kLoadVariables = 0x40,
 kGetStrings = 0x80,
 kTraceTribes = 0x100,
 kTraceOSLVariables = 0x200, // CFixed::PrintFileDebug should print OSL Variable Names the hard way
 kTraceMathResult = 0x400,
 kTraceList = 0x800, // Attempt to show list content

The output shows addresses and opcodes, showing its focus on debugging the language:

OSL 0xd8b694 Running from 0, fRunDepth 0:
-------------------------------------------
0000 : 0202 019E Picture "scene018" 
0002 : 0203 0001 Position 0001 
0005 : 4005 E
 Loading Variable 4005 (E) 0.000 (gValue)
0006 : 0800 = 
0007 : 068D RandomElement 
0008 : 0480 ( 
0009 : 070E ClanMembers 
000A : 0841 - (subtract) 
000B : 0718 RingMembers 7ffa0000 -- P00000000:0021a700 (gValue)
 Result: 7ffa0000 -- P00000000:005e18fe (gValue)
000C : 0481 ) -- gValue = 7ffa0000 -- P00000000:005e18fe (gValue)7ff20001.0000 : Brenna (gValue)
 Setting Variable 4005 (E)
 Result: 7ff20001.0002 : Brenna (gValue)
000E : 4021 otherClan
 Loading Variable 4021 (otherClan)7ff30017.0000 : Blue Jay (gValue)
000F : 0800 = 
0010 : 068D RandomElement 
0011 : 0480 ( 
0012 : 068A NeighboringClans 
 4 COSL::Neighbors:
Clans:
 9 Boskovi
 15 Blackrock
 18 Greydog
 23 Blue Jay

0013 : 0481 ) -- gValue = 7ffb0000 -- C00000000:00848200 (gValue)7ff30012.0000 : Greydog (gValue)
 Setting Variable 4021 (otherClan)
 Result: 7ff30012.0000 : Greydog (gValue)
0015 : 4012 R
 Loading Variable 4012 (R) 0.000 (gValue)
0016 : 0800 = 
0017 : 0600 FALSE 0.000 (gValue)
 Setting Variable 4012 (R)
 Result: 0.000 (gValue)
0018 : 0212 01A4 Saga "<4005> told us we should take in Orlkensor Bronzebones, a warrior outlawed by the <21>.plural." 
 Loading Variable 4005 (E)7ff20001.0002 : Brenna (gValue)
ReplacePlaceHolders...7ff20001.0002 : Brenna (gValue)
 Loading Variable c321 (otherClan)00000000.0000 : Greydogs (gValue)
ReplacePlaceHolders...00000000.0000 : Greydogs (gValue)
001A : 0201 01CF Music "IsItAdventure" 
001C : 0A00 NewChoices

Once OSL was reliable, it turned out that the log was useful to help debug the OSL scripts themselves. If something weird happened, you could see what code branch was taken, and what some of the variables were. For example, the output above shows all the neighboring clans

And we output other information to the log, such as the scene queue, some of the clan decisions, and more.

The debug log can grow quite large (a quick search shows one at 7.7 MB), so the game makes a new one every time you launch. But if you had to relaunch because of a crash, that would mean that any evidence would be deleted. So actually, we rename the log, and actually delete the previous one.

While I reworked OSL for Six Ages, I based it on Shawn’s work, and wasn’t so concerned about debugging the language itself. Instead, I wanted to focus on the scripts, since that seemed like where most of the bugs would be.

<OSL: 0x170194ec0> ® 2 Affiance her to …
-----------------
: Saga "We affianced her to <.an> <ourClan> groom." {a} {C_1 Arrowstone}
: (ourClan).commoners {277}
: += 
: # 1 {277} {278}
 ourClan.commoners ← 278
: ChooseYesNo
: ( 
: String "Do you accept the 10 cows?" 
: ) ↖︎ = Do you accept the 10 cows?
sendToCurrentScene: kNewChoice
… exploded
Restarting OSL (self.result=kNewChoice)

This is more compact (thus easier to read): more values are shown on the same line, and it doesn’t bother to show addresses or opcodes.

The next post will talk more about how to make use of this.

Where The Money Goes

A comment on our last status update mentioned our budget. Our plan is to make a new game for a lot less than it cost to do King of Dragon Pass. Hopefully our experience (with KoDP and games in general) will let us do things more efficiently this time. We will likely do a lot less reworking of certain elements than we did the first time, since we have a better idea ahead of time what should work. And automated testing will let QA concentrate on bugs only a human can find.

budgetplanThe pie chart shows where we expected to spend our budget shortly after starting. The bulk of the expense (44%) was projected to be art.

Note that this doesn’t include any sort of overhead (like development machines, internet access, etc.). And I’m not directly expensing my own time. In other words, this is just what we expect to pay our freelancers.

This was just a plan, and once we were far enough along to see how things were working, we realized had to do more writing and art. So the final percentages will end up a bit different, though art will predominate.

(And yes, the title of this post is an excuse to watch OK Go.)

October Progress Update

troll warrior leans on a spearI’ve been working full time on Six Ages for over two years, but it’s still not done. How far along is it?

Answering that question is a little hard, partly because I haven’t devoted as much time to managing the status as I have on creating the game. I am pretty sure of one thing: it will not be released this year, as I had originally hoped.

Other than that, I’m pleased with the progress. One important reason is that the budget needed to be significantly less than King of Dragon Pass, and even as development continues longer than I had planned, we’re still OK on costs. And game play is complete enough that you can play a complete game, so we can start improving that. And complete enough that we can tell a lot of things are working pretty well.

One reason that things are taking longer than my original plan is that as we developed the game, it became obvious that it would need more scenes and accompanying art than I had first thought. We now have 40% more scenes, and 59% more scene art, in fact!

More scenes means not only more art, but more time needed to write them, more time needed to code them in our scripting language, and more time needed to make sure that all branches are tested. As I’ve written before, we are done with the writing. We’ve also completed script coding. Testing is harder to measure (since some branches depend on external conditions or chance), but over 3/4 of scenes have had all branches exercised. (Our automated testing isn’t as thorough, though it adds another level of confidence.)

Children cheer, dressed for winterIf the art needs hadn’t grown, we would be completing the scene art this month. But there are more scenes. And on top of that, since it’s a different game than King of Dragon Pass, we can’t reuse some art in the same fashion. So there’s still a bunch to go. On the other hand, we have a lot of really good art.

The game is certainly far enough along to start its tutorial. The first draft is nearly complete. Thanks to those who gave input!

Besides finishing the art, a few big tasks remain: music, manual, user interface polish, and a lot of game tuning. That will clearly take us past the December holidays and into next year.

Tutorial Planning

We’re screen-shot-2016-09-29-at-19-53-09 in the process of working on the game’s tutorial.

We’re starting a bit earlier than we did with King of Dragon Pass (since the game is not done), and we’re also taking a somewhat different approach.

King of Dragon Pass had a scripted tutorial, which tried to take you on a tour of the important elements of the game. But it was too easy to get off the rails and either miss things, or confuse the tutorial. (And as a side note, it was actually not the best first play experience since you raided during planting season — we wanted the tutorial to mention this, but if you then continued the game, you were in a small hole.)

Six Ages will take a more reactive approach. Instead of telling you to switch to the Map screen, and then describing exploration, it will wait until you switch on your own, and then describe exploration. It will also (for example) mention the size of a reasonable sacrifice the first time you’re asked to sacrifice. So you’ll get the information when it actually matters.

One thing I’d like to do is ask King of Dragon Pass players what confuses you about the game. For example, we once got a bug report that clan magic went negative. That’s actually by design, but if it confused someone, we can pop up a tutorial card when it happens. I expect we’ll have a manual too, but if something seems wrong, you’re not going to go look it up.

So please let us know what confused you or you think needs better explanation, here in the comments (or to @SixAges on Twitter).

Treasures

Like King of Dragon Pass, Six Ages will include magical treasures. They’re Gloranthan (they were in the original White Bear & Red Moon game, and lots more in the subsequent Nomad Gods), and they are a useful reward and fun to collect.

And they’re essentially all unique, which is great as a player but less wonderful as a developer.

As a developer, I want treasures to be easier to work with this time. KoDP had some annoying limits on how many there were (adding a treasure meant breaking the file format of saved games), which was an obvious thing to improve. But KoDP had the implementation of treasures scattered everywhere. Basically, each treasure needed specific C++ code, plus it might have to be listed in an IsBattleTreasure or TreasureThe (it’s “Vinga’s Comb,” not “the Vinga’s Comb”) function.

First, we designed as many as possible with script tags in mind. In other words, Robin Laws would describe a treasure’s effects as “+1 to Bargaining and Diplomacy in scenes tagged @outlanders.” Then, I’d specify treasures as Lua data like

modifiers = { Bargaining = 1, Diplomacy = 1 },
requires = { "@outlanders" }

so there’s no treasure-specific code. Compare to KoDP having to insert lines like

case code_Leadership:
    if (ourClan->HaveTreasure(kRingMadeFromVingkotsCrown))
        skill++;

Part of the advantage is that the details of the treasure effect are all in one place, next to metadata (such as name, description, value, use of “the,” and whether a treasure needs to be chosen in battle).

The data-driven approach also handles things like “mood+=13 on any Food win”

onWin = {"Food", "mood", 13},

Combat has some additional complexity, so “+1 Combat when raided by elves” is the Lua table

battle = { type = type_Raid, defender = true, opponentStereotype = "aldryami", bonus = 1 },

To summarize: we tried to design to a number of patterns, wrote general-purpose code to handle the patterns, and then specify how a treasure slots into the pattern with Lua data.

Of course, magic doesn’t always fit a pattern, so we can run an OSL script

everyTurn = "fragment_MostaliMill"

or have a script check for a treasure

code: fragment_SacredTime
[HaveTreasure('bowlOfUnity)] {
    # .magic +=1 each Sacred Time if Expansiveness>0

Not quite as good as pure data but better than requiring code.

Quasi-Treasures

A treasure is now a way to encapsulate a conditional modifier. I started to notice another pattern, where player choice could result in a modifier. Rather than come up with a new scheme, I just made new treasures. (I called them quasi-treasures because they act like treasures but don’t look like treasures. Notably, they don’t appear in lists of treasures in the user interface.) In fact, one of the examples above is actually a quasi-treasure.

Treasures are typically gained permanently and quasi-treasures often go away after a certain number of years (for example, the visiting priest’s blessing lasts five years). This can be handled by the normal GainTreasure/LoseTreasure functionality (i.e. just queue a script to run in five years that executes the LoseTreasure function).

malletOfPlentyPictures?

We discussed showing treasures, and Jan mocked up a treasure illustration. However, I decided against a visual representation. It’s a lot of work just designing what dozens of treasures look like, let alone getting all the pictures. More importantly, it would increase the difficulty of adding new treasures in an update (which we did twice in KoDP, despite the hassle), since they would need pictures.

So this is the only place you’ll see the mallet…