Development Progress Report, week 29

My, what a productive week it has been! Many bugs fixed, and many new bugs created (and also fixed).

If we were just working off bugs-fixed, this would have been a banner week, indeed, since I was briefly creating them as fast as I resolved them. However, there was a whole lot more going on.

Game-restarts are exciting!

It’s the wrong kind of excitement, though.

It isn’t until relatively recently (a month, perhaps?) that I wrote code to allow the story to be restarted. Previously, when the story ended, so did the application. Instead, I wanted the story to return to the character-selection/title screen, so that the story could be played again without having to relaunch the application.

A little later again, I added a manual restart option in the options/preferences screen.

There have been … issues.

Argus has a whole lot of fairly intricate data-storage. I’ve certainly worked with partial cleardowns and reloads for saving and restoring, but actually resetting everything to the original character-selection state (as opposed to a running-the-game state) involves some extra steps that I missed.

One of those steps is the initial values of certain variables. The original code, as written, assumed a run-once methodology. That meant that certain globals and data-structures were assumed to contain either good data (in running mode) or their default initialised values (in non-running mode). Those default values were often some variation of zero. (More about modes in the next section)

If they were at their defaults, we knew what to do when we were tooling up. If they were something else, we assumed they were set right. Failure is forgetting to reset certain globals to their starting values when resetting the story on-the-fly.

In some cases, the current_scene pointer wound up pointing to released memory that still contained valid data. In other cases that data had been overwritten. The latter would crash the game, but both were equally bad. Forgot to set the danged pointer to null when we were resetting the game. If we were loading a save, that wasn’t an issue, as altering that pointer was a part of the deal.

Another case is the narrator’s pending scene list, and I believe I just fixed a hard-to-reproduce bug with game-restarts just now. Gut feel says the pending scene list wasn’t cleared (and it wasn’t) and that could be an issue with game-restarts (it certainly can!). My theory is that lingering scenes in the pending list caused one or two scenes to run out of order when the restarted-game commenced.

Unfortunately, the bug is hard to reproduce, so I’m not actually certain that it is fixed. It probably is. たぶん. We’ll see.

Note to self: Audit the Narrator’s internal globals, and make sure there’s a function to restore them all to their default states.

Narrative modes vs UI modes

Back when I first prototyped this code, it was little more than a simple console application that spat text out in response to keypresses. As it advanced, the core narrative engine acquired several modes: Loading, running, ended, waiting-for-a-human-to-make-a-choice, and the like.

Once the user-interface started getting properly built, that mode list expanded to include tracking things like scroll-back mode, popups, footnotes and more.

This was a major mistake.

Realistically-speaking, the Narrator modes shouldn’t include anything to do with the assorted modes of the user-interface. In all other respects, the narrative engine sits behind a code-wall, where it should have no knowledge of what’s going on over on the UI side of the wall.

Except when it came to modes.

As the UI became more intricate, this caused problems. The Narrator needed to know too much by way of its mode variable, and the UI level had to keep track of previous Narrator modes so that they could be restored.

Well, I had quite enough of that.

For version 0.8.191 (Tuesday), I ripped all of the UI-related items out of the narrative mode tracker, and created a separate and distinct set of modes for the UI. This turned out to be surprisingly involved. Between that and a few other changes for this particular version, I wound up reworking about a quarter of the lines of UI code.

The end result, was great. Except for that infinite loop bug that occured because I’d missed two lines of code that tap-danced around some of the old issues in mode-tracking for save-games.

That, alas, didn’t get fixed until 0.8.231 (Friday). An alpha-tester reported the issue, but I didn’t get any information that might help me reproduce it. Then, by chance, I saw it myself on Friday morning, and instantly knew where the problem was, what was causing it, and how to fix it. Obvs, as the cool kids say, these days. (They still do that, right?)

Five minutes later, it was sorted.

Sliders

My alpha-tester also asked for volume controls. They had certainly been on my to-do list for a while, and I had been planning to implement some sort of slider widget for just that purpose.

Getting actual feedback from a tester helped spur me to doing the job.

How often do you actually think about how pixel-coordinates in a slider actually map to ranges or gradations of values? Probably not often. I certainly hadn’t given it much thought, but I sat down and thought about it a lot.

A dozen attempts (with varying degrees of failure) later, I had a working slider widget that did the job, in a measly dozen or so lines of code. Sliders are about three generations down the widget inheritance tree, so there’s a lot of useful stuff already there for them, which keeps the code from bloating out.

Then I iterated on them, making them more attractive, more responsive, and a lot closer to intuitive. It’s actually pretty easy to make unintuitive UI widgets, but it only takes a little more effort to make them behave in ways that won’t surprise or confuse the user. Take that time.

By Friday’s build, they could even be styled in various ways. I took the opportunity to replace the text delay widgets (three of them: value display, increase, and decrease) with a slider as well, which works and feels a whole heap better. You can see an example of them in the image at the top of this post.

Bits and pieces

As a part of the big overhaul, I replaced almost all of my instances of SDL_Rect with a rectangle class of my own, converting to SDL_Rects only when necessary. The class neatly initialises to zero and has all of my common rectangle-manipulation functions built-in. Wherever it goes, they go.

That accounted for a lot of lines of code getting rewrites, but the results were considerably simpler, and – in the end – more optimal, it seems.

Sliders needed a bit more supporting code during the iteration phase, so I also took the opportunity to build a few more features into the basic widget class, which allowed me to significantly simplify and optimise the rendering and access code for many of my widgets.

Note to self: This is probably not the sort of stuff you should be doing between Alpha Demo Release Candidates. Save it for between-releases.

Additional note to self: Stop overhauling/extending/rewriting code before a build has even finished uploading! This is important!

Added some additional visual glitz that can be triggered from stories. Mostly just fade-in/fade-out effects and some new art for one of the prologues. Proper character art is still missing, but there’s placeholders for most of the major characters, for now.

Another thing I started tackling was improving the syntax-checking in the story-compiler. Of the (currently) 32 available actions, I added some syntax-and-sanity checking for the three most common. Bad news: It flagged about 40 issues. Good news: It took a little under four minutes to fix all of them.

I’ll need to extend that system quite a bit more, especially if anyone else is ever going to make stories for this engine.

I switched out a lot of code that used RGBA values and used a colour-lookup table for them, using the stock set of 140 HTML colour names/values (plus two). Had to grab the data and reprocess it so that it could be dropped cleanly into the code as a table of colournames and corresponding RGBA values.

If you ever want to do this yourself, here it is. It’ll save you a few minutes:

 { L"trans",{ 0,0,0,0 } },
 { L"battleshipgrey",{ 132,132,130,255 } },
 { L"aliceblue",{ 240,248,255,255 } },
 { L"antiquewhite",{ 250,235,215,255 } },
 { L"aqua",{ 0,255,255,255 } },
 { L"aquamarine",{ 127,255,212,255 } },
 { L"azure",{ 240,255,255,255 } },
 { L"beige",{ 245,245,220,255 } },
 { L"bisque",{ 255,228,196,255 } },
 { L"black",{ 0,0,0,255 } },
 { L"blanchedalmond",{ 255,235,205,255 } },
 { L"blue",{ 0,0,255,255 } },
 { L"blueviolet",{ 138,43,226,255 } },
 { L"brown",{ 165,42,42,255 } },
 { L"burlywood",{ 222,184,135,255 } },
 { L"cadetblue",{ 95,158,160,255 } },
 { L"chartreuse",{ 127,255,0,255 } },
 { L"chocolate",{ 210,105,30,255 } },
 { L"coral",{ 255,127,80,255 } },
 { L"cornflowerblue",{ 100,149,237,255 } },
 { L"cornsilk",{ 255,248,220,255 } },
 { L"crimson",{ 220,20,60,255 } },
 { L"cyan",{ 0,255,255,255 } },
 { L"darkblue",{ 0,0,139,255 } },
 { L"darkcyan",{ 0,139,139,255 } },
 { L"darkgoldenrod",{ 184,182,11,255 } },
 { L"darkgray",{ 169,169,169,255 } },
 { L"darkgreen",{ 0,100,0,255 } },
 { L"darkkhaki",{ 189,183,107,255 } },
 { L"darkmagenta",{ 139,0,139,255 } },
 { L"darkolivegreen",{ 85,107,47,255 } },
 { L"darkorange",{ 255,140,0,255 } },
 { L"darkorchid",{ 153,50,204,255 } },
 { L"darkred",{ 139,0,0,255 } },
 { L"darksalmon",{ 233,150,122,255 } },
 { L"darkseagreen",{ 143,188,143,255 } },
 { L"darkslateblue",{ 72,61,139,255 } },
 { L"darkslategray",{ 47,79,79,255 } },
 { L"darkturquoise",{ 0,206,209,255 } },
 { L"darkviolet",{ 148,0,211,255 } },
 { L"deeppink",{ 255,20,147,255 } },
 { L"deepskyblue",{ 0,191,255,255 } },
 { L"dimgray",{ 105,105,105,255 } },
 { L"dodgerblue",{ 30,144,255,255 } },
 { L"firebrick",{ 178,34,34,255 } },
 { L"floralwhite",{ 255,250,240,255 } },
 { L"forestgreen",{ 34,139,34,255 } },
 { L"fuchsia",{ 255,0,255,255 } },
 { L"gainsboro",{ 220,220,220,255 } },
 { L"ghostwhite",{ 248,248,255,255 } },
 { L"gold",{ 255,215,0,255 } },
 { L"goldenrod",{ 218,165,32,255 } },
 { L"gray",{ 128,128,128,255 } },
 { L"green",{ 0,128,0,255 } },
 { L"greenyellow",{ 173,255,47,255 } },
 { L"honeydew",{ 240,255,240,255 } },
 { L"hotpink",{ 255,105,180,255 } },
 { L"indianred",{ 205,92,92,255 } },
 { L"indigo",{ 75,0,130,255 } },
 { L"ivory",{ 255,255,240,255 } },
 { L"khaki",{ 240,230,140,255 } },
 { L"lavender",{ 230,230,250,255 } },
 { L"lavenderblush",{ 255,240,245,255 } },
 { L"lawngreen",{ 124,252,0,255 } },
 { L"lemonchiffon",{ 255,250,205,255 } },
 { L"lightblue",{ 173,216,230,255 } },
 { L"lightcoral",{ 240,128,128,255 } },
 { L"lightcyan",{ 224,255,255,255 } },
 { L"lightgoldenrodyellow",{ 250,250,210,255 } },
 { L"lightgreen",{ 144,238,144,255 } },
 { L"lightgrey",{ 211,211,211,255 } },
 { L"lightpink",{ 255,182,193,255 } },
 { L"lightsalmon",{ 255,160,122,255 } },
 { L"lightseagreen",{ 32,178,170,255 } },
 { L"lightskyblue",{ 135,206,250,255 } },
 { L"lightslategray",{ 119,136,153,255 } },
 { L"lightsteelblue",{ 176,196,222,255 } },
 { L"lightyellow",{ 255,255,224,255 } },
 { L"lime",{ 0,255,0,255 } },
 { L"limegreen",{ 50,205,50,255 } },
 { L"linen",{ 250,240,230,255 } },
 { L"magenta",{ 255,0,255,255 } },
 { L"maroon",{ 128,0,0,255 } },
 { L"mediumaquamarine",{ 102,205,170,255 } },
 { L"mediumblue",{ 0,0,205,255 } },
 { L"mediumorchid",{ 186,85,211,255 } },
 { L"mediumpurple",{ 147,112,219,255 } },
 { L"mediumseagreen",{ 60,179,113,255 } },
 { L"mediumslateblue",{ 123,104,238,255 } },
 { L"mediumspringgreen",{ 0,250,154,255 } },
 { L"mediumturquoise",{ 72,209,204,255 } },
 { L"mediumvioletred",{ 199,21,133,255 } },
 { L"midnightblue",{ 25,25,112,255 } },
 { L"mintcream",{ 245,255,250,255 } },
 { L"mistyrose",{ 255,228,225,255 } },
 { L"moccasin",{ 255,228,181,255 } },
 { L"navajowhite",{ 255,222,173,255 } },
 { L"navy",{ 0,0,128,255 } },
 { L"oldlace",{ 253,245,230,255 } },
 { L"olive",{ 128,128,0,255 } },
 { L"olivedrab",{ 107,142,35,255 } },
 { L"orange",{ 255,165,0,255 } },
 { L"orangered",{ 255,69,0,255 } },
 { L"orchid",{ 218,112,214,255 } },
 { L"palegoldenrod",{ 238,232,170,255 } },
 { L"palegreen",{ 152,251,152,255 } },
 { L"paleturquoise",{ 175,238,238,255 } },
 { L"palevioletred",{ 219,112,147,255 } },
 { L"papayawhip",{ 255,239,213,255 } },
 { L"peachpuff",{ 255,218,185,255 } },
 { L"peru",{ 205,133,63,255 } },
 { L"pink",{ 255,192,205,255 } },
 { L"plum",{ 221,160,221,255 } },
 { L"powderblue",{ 176,224,230,255 } },
 { L"purple",{ 128,0,128,255 } },
 { L"red",{ 255,0,0,255 } },
 { L"rosybrown",{ 188,143,143,255 } },
 { L"royalblue",{ 65,105,225,255 } },
 { L"saddlebrown",{ 139,69,19,255 } },
 { L"salmon",{ 250,128,114,255 } },
 { L"sandybrown",{ 244,164,96,255 } },
 { L"seagreen",{ 46,139,87,255 } },
 { L"seashell",{ 255,245,238,255 } },
 { L"sienna",{ 160,82,45,255 } },
 { L"silver",{ 192,192,192,255 } },
 { L"skyblue",{ 135,206,237,255 } },
 { L"slateblue",{ 106,90,205,255 } },
 { L"slategray",{ 112,128,144,255 } },
 { L"snow",{ 255,250,250,255 } },
 { L"springgreen",{ 0,255,127,255 } },
 { L"steelblue",{ 70,130,180,255 } },
 { L"tan",{ 210,180,140,255 } },
 { L"teal",{ 0,128,128,255 } },
 { L"thistle",{ 216,191,216,255 } },
 { L"tomato",{ 255,99,71,255 } },
 { L"turquoise",{ 64,224,208,255 } },
 { L"violet",{ 238,130,238,255 } },
 { L"wheat",{ 245,222,179,255 } },
 { L"white",{ 255,255,255,255 } },
 { L"whitesmoke",{ 245,245,245,255 } },
 { L"yellow",{ 255,255,0,255 } },
 { L"yellowgreen",{ 169,205,50,255 } }

You’re welcome 🙂

Further development

Still haven’t quite hit the next narrative milestone, but I’m almost there, which means I’m starting to think more about how the Narrator will be triggering future story arcs, and scheduling scenes.

The existing content uses a fairly basic version of this – a prototype system, really, based on times and precondition expressions to maintain narrative order and continuity of scenes.

To take this to the next level, that’s going to have to get a whole lot more complex. Neither the prototype scheduling/triggering system, nor what I’m thinking the next version will look like, is very much like the system as I originally envisioned it. The original vision was a bit more focused on an underlying simulation model that – in practice – I don’t think makes as much sense.

The new system, I think, will focus more on actual narrative requirements. When and where should scenes take place? How do we avoid narrative clashes or narrative deadlocks? How can the Narrator guide the story into a cohesive telling? How much of that work devolves onto the author?

Obviously a big part of this is data-representation.

Currently, every scene has a block of metadata at its head that describes scheduling, casting, preconditions, arc information, and requirements. That’s definitely going to have to expand somewhat, and I’m sure I am going to need a timeline for the Narrator, as well as some sort of directed acyclic graphs to represent both current and potential story-structures.

Just how much those graphs will need to be twiddled at runtime and how much can be generated by the story-compiler remains to be seen. Personally, I’m hoping the story-compiler can handle the bulk of the heavy-lifting. たぶん.

The way it looks to me right now, the Narrator will probably only be able to do its job properly if it can work both backwards and forwards through both currently-scheduled story-structure, as well as future-potential story-structures.

It sounds extremely complicated to me right now, but like most things with this engine, it probably actually won’t be once the code is written and running.