Development Progress Report, week 67

I spent a bit over a week being pretty sick, so week 67 slipped one cycle.

It’s been a bit slow getting back into the groove, so let’s do something a little different this week.

Dubious design-decision theatre

In software-development every single design decision that you make is going to narrow your options. There’s this gigantic tree of possibilities growing from your first choice. Some choices eliminate small groups of possibilities. Some eliminate thousands.

Every choice makes something harder or easier (or functionally impossible) further down the track.

So, let’s talk about Oids.

Argus compiles a bunch of source-files and assets into a ready-to-use set of story files. While the compilation is far from traditionally-structured, the end result is broadly similar to what you’d expect from a byte-code compiler: A more compact representation that is easier and faster to interpret and exectute.

That wasn’t always the case. The earlier versions of Argus interpreted the raw story source-text directly, and that’s where one of my earlier design-decisions came into play.

Argus uses Oids (Object-IDs) to keep track of important things. Every actor in the story has an Oid. Every location has an Oid. Every scene has an Oid. Every role within a scene has an Oid (and there’s a table mapping scene-relative role Oids to absolute actor Oids).

Oids started out simply enough. An Oid was fundamentally an integer. An Oid value of zero meant “no object” (and was used as an error value if a lookup failed).

Originally, Oids started out as a single 64-bit integer. Enough Oids for everyone forever, I was thinking.

Then theory met practice. I’d block out broad scene-structures, and assign numbers to those scenes. Then I’d start writing and find that either technically or narratively, I needed additional scenes in various parts of the narrative.

Well, it turns out that renumbering scenes is a serious nuisance. You have to renumber the scene, and all of the references to that scene, and doing so messes up any saved game with the old scene-structure. Scenes can be deleted (sort of), or added, but just changing the Oid on one is potentially fraught.

The alternative was to add new scenes elsewhere, and that seemed aesthetically annoying.

I’d started with scene Oids with numbering intervals of ten, so that I’d have space for up to 9 more scenes between them.

Then I would up with situations like scene 360 which rapidly started filling the space between 360 and 370 with additional scenes and subscenes. And then I simply ran out of space for more Oids in that range, before I ran out of scenework.

So, one of those scenes wound up in another part of the story-file as scene 282.

Aesthetically, this was somewhat offensive.

So, I decided to actually hack on Oids.

Instead of a bare integer, like 1, 2, or 3, what if I could add a second integer to an Oid, to further subdivide the domain?

So, Oids became two 16-bit integers, with a simple syntax: 10.1, 10.2, 123.7 – but you could still leave the second part off and use the original “naked” syntax. Internally 1, 2, or 3 simply were treated as 1.0, 2.0, or 3.0.

This worked extraordinarily well. I refactored the Oid class, and updated the parser slightly to allow for the optional dotted syntax, and it worked like a charm. It’s aesthetically pleasing, moderately compact, and allows me to cluster like things together more efficiently. Cheap and cheerful, as they say.

Fast-forward to now.

A whole lot of parsing work has been shifted from the main game-engine to the story-compiler. My general rule is that if I can save space and cycles by moving work into the story-compiler, I’m doing that.

Action tokens and other key pieces of syntax are converted to single values which are light on storage, and both quick and easy for the game-engine to process.

That’s great. Except for Oids.

Oids stick out because they’re one of the remaining human-readable strings that are left at the metadata/control level.

My gut instinct is to convert them to a straight binary format, but I’ve narrowed my choices down to the point where this actually isn’t going to be very useful.

From a simple size-perspective, the plain-text decimal form is actually quite compact. In most cases a binary form would waste a lot of space per-Oid. Yes, I could use different binary representations for differently-sized Oids (or for single-value Oids vs bi-value Oids), but then I have to signal which of several sizes/types of Oids we’re expecting to see in the data-stream.

And that basically loses our potential efficiency gains.

I could compactify the representation by switching to an alternate base (rather than leaving it in decimal), but in some cases digits are used to distinguish the presence of an Oid from the presence of a number of other plausible alphabetical labels. So, again, sweeping changes for little effect.

Perhaps some other schema or representation will occur to me, or perhaps I’ll be stuck with the consequences of those early choices. It’s not the worst of all corners to have painted myself into, as the performance of Oids as-is is already excellent, and it is only my sense of elegance that is being rubbed the wrong way.

Absolutely none of this is insurmountable. There are probably dozens of ways I could approach this, but the costs of doing so would now be disproportionately high compared to any real gains.

If there’s a lesson here (and there are always lessons), it’s to pay attention to how today’s design-decisions are going to affect your range of choices tomorrow, next week, next month, or next year.

The effects aren’t necessarily bad or wrong. Practically every choice ultimately proves to be a limit or a cost later on. The trick is to make sure your limits and your high-costs are outside of the possibility-space that you ultimately want to be working in. Look ahead and don’t make future-you’s life more difficult. Make it easier.