Degeneracy Post-Mortem

"You enter what seems to be an older, more primitive world."
--Nethack, versions 3.1.0 and up

Spoiler warning

Spoilers ahead. Turn back!

The Idea

In July of 2000 I started writing software professionally, and became acquainted with the wonderful world of version control. I immediately wished I had a CVS repository set up for "Guess the Verb!", the game I was then developing, so that instead of packaging up source releases of the game every now and then, I could just point source-code-wanters to the CVS repository.

In November, after Guess the Verb! had been weighed in the balance and given 11th place in the 2000 IF Competition, I decided it was time to start work on my sophomore effort. I had a silly idea which I was just about to start experimenting with, when I came up with an even sillier idea: a game which changed its version number as you played it. As I conceived it, you would start at the latest version, and as the version number slid inexorably downwards, objects would disappear, typos would pop up, and previously fixed bugs would come back like Jason back for another try.

It took about thirty seconds for me to connect this idea with the "world in decay" scenario often seen in fantasy games. I got a piece of paper and drew the most generic castle layout I could think of: three rooms square, four towers in the corners for battlements (two of these went below ground to become the store room and the priest's cell, and the other two disappeared) I numbered each room and made each one instantiate a class whose room description merely listed the exits. This was basically version 0.01. To get a feel for the next fifty-plus versions, look at my list of fixed bugs, or just play the game.

I tied it into the plot, of course. The idea is that you have been sent on a mission to kill this corrupt baron, but by killing him you trigger a curse intended to trap you in his slowly disintegrating keep (conveniently destroying the evidence of his wrongdoings at the same time). You can break the curse and leave, or you can choose to stop the deterioration of the game world on your way out. The title of the game is a reference both to this process and to the state of moral turpitude that occasioned the baron's assassination.

The Implementation

It's one of the ideas implementation of which you only embark upon because you don't quite realize how much work it's going to be, and how much of that work will be tedious bookkeeping. It's all very well to say "I'm going to write a game which contains its own CVS repository.", but it's quite another to put the architecture in place, and still another to make sure that the architecture gets used consistently throughout the game. I occasionally cheated and made a change retroactive, especially when not doing so would have had horrible repercussions on the game. [0] But most of the bugs are still in there, and a lot of them let you do things you shouldn't be able to do (for instance, in versions previous to 0.34 you can use the little box of spices to hold anything, even huge things like the dead pig and Baron).

The architecture is as follows: almost object in the game subclasses the VCObject class. This contains a version number at which the object is first implemented; when the version number drops below this number, the current location is noted (so that if the version number ever goes back up it'll reappear in the right place), and the object gets moved to a storage room. VCObject also implements methods called downgrade and upgrade; these are called whenever the version changes upwards or downwards, to effect whatever changes in internal state are neccessary (for instance, some objects gain attributes in certain versions; this requires a downgrade which removes the attribute in version x-1 and an upgrade which grants it in version x).

VCObject also implements a parse_name which calls VCObject's wordOkay for each relevant word. This made parser-level version control much easier; when I was told by a beta tester that the Baron's corpse should be referrable to as "body", I implemented recognition of "body" only in versions 0.50 and up by putting a conditional in the corpse's wordOkay. I didn't have to implement the (marginally) more complicated parse_name, since I was using VCObject's parse_name.

In addition, there are several utility functions for handling changes to text, which I wrote as I recognized a need for them. change(oldtext, newtext, version), insert(text,version), and delete(text,version) handle a wide variety of common actions, such as the correction of my misspelling of "Archimedes" in version 0.44 (so that it shows up "Archimedes" in versions >= 0.44 and "Archimides" in versions < 0.44). There's also amp(version), which prints "&" in version and above, and "and" below that version; I committed this stylistic inconsistency so much I wrote a special method for rectifying it as of a particular version.

My original plan was for the degeneracy to be irreversable. It would be much simpler that way; reversal meant keeping track of where objects were when they disappeared, and was complicated by containers which disappeared before the things they contained (such as the copper key, which sits on the dresser in the Baron's room but which was implemented before the dresser). State changes need to disappear along with the code to implement them. For instance, starting in version 0.40 you can chop up the dented box with the axe. If you chop up the box and then go before version 0.40, the box reverts to being un-chopped up. If you later get the version back up to 0.40 or above, the box needs to become chopped up again.

None of this is difficult, programming-wise, but it is tedious, and very few people will ever see any given example. However, once I'd given the degeneracy a physical manifestation in the form of the hourglass, a puzzle reversing the degeneracy by refilling the hourglass was an obvious step, so I went ahead and did it. The degeneracy is now arbitrarily reversible and... well, versible. I hope. There are probably still some bugs hiding in there, but I doubt anyone will ever go through the trouble of finding them.

What Went Right

First, the concept, in all modesty, is really cool. As Vyvyan from The Young Ones once said, "That's the most completely brilliant thing I've ever seen! A flying shark!". Well, it's not as cool as a flying shark, but it's still pretty nice.

I love the keys. In so many games (IF and otherwise), the extent of the puzzle-solving component is finding colored keys to open colored locks. I thoroughly enjoyed creating puzzles in which you obtain colored keys which are completely worthless for lock-opening purposes. (Well, you need to use the gold key to open the trapdoor, but I also love objects used in multiple puzzles, even if one puzzle is a lame door unlocking puzzle. Now that I think about it, though, I should have left the trapdoor unlocked.) I also liked implementing a standard lock-and-key puzzle (the priest/box puzzle) in which you employ an otherwise worthless locked object to obtain its (useful) key.

Though this is a topic of some contention, I like the inventory system. I did not want the player to be able to simultaneously carry an axe, two large corpses (pig and human), a mechanical fountain, a fistful of keys, and a long lance. A lot of the objects in the game are so heavy that you could not reasonably expect to be able to carry anything else, and it's a small step from that to a hand-based system that lets you hold smaller things in greater quantities. Also, there are a couple effects which wouldn't work without the sort of system I implemented: the hourglass is so heavy that you can only hold it for a few turns, and the book numbs the hand you use to carry it.

I'm fairly pleased with the cluing-in that no, the game is not ridden with bugs. Originally there was absolutely no cluing-in of this sort. [1] This started changing when beta testers started running into the problem that they knew something was going on, but they had no idea what. The creepy messages you get when you re-enter a room you've been away from for a few versions, and the travails of the hourglass as it slowly runs out of sand, were added in the final release candidates to help clue the existance of something beyond the obvious, and to tie that into the actions of the hourglass.

After the feisty twelve-year-old PC of Guess The Verb! I found it a nice change to do an PC with singleness of purpose who took everything very seriously. I wanted an assassin, but not one who was conflicted about his assassinations, but not one who was unconflicted due to having no moral compass. So I made the PC a knight well-versed in the faith (one thing I should have made a bigger deal out of was the fact that the PC knows how to read), someone able to execute God's will in the full knowledge that it involves committing a mortal sin.

Finally, I like the style I used in the game. It was a lot of fun writing the purple prose in the room descriptions. I'm also pleased with the integration of period ideas (sympathy and perpetual motion) into the puzzle system.

What Went Wrong

The puzzles are not particularily hard, and in general they're the sort of puzzle that you think of early on in development and then replace with better puzzles which are more intuitive. Of course, I couldn't do this without totally messing up the playability of the game or violating the guiding principle which I was using to write the game. So I basically had to dump in clues until the puzzles were solvable without reading my mind. This turned the centerpiece puzzles into basically "look at the reference materials and suddenly make logical connection", which would be great in a Name of the Rose adventure game, but this isn't that game.

I'm all for rich reference material, but you shouldn't have to check six library entries to figure out what to do in a puzzle. One person complained on the newsgroup that the formula for rose gold is never given; it is given, but in the entry for "rose gold", not in the entry for "sympathetic alloy", even though the two are the same thing. I had fun writing the library reference text, but the important stuff is too split up across slightly different entries.

The lance and the hole behind the desk is total AGT puzzle. I mainly put it in because I needed a use for the armo[u]ry. The puzzle might serve a useful purpose in subverting the player's train of thought when it comes time to use the keys, but probably not, since you can't see the results of your puzzle solving until you've gotten through all the doors. So, pretty lame, but at least internally consistent with the game world.

I don't think I did enough to clue the fact that the room descriptions and objects are disappearing because the game is slipping backwards in implementation time. The HELP menu displays the current version, and that's the biggest hint; people who are sweating to figure out the game are more likely to try HELP more often, and hopefully with the version number up there every time they do, they'll eventually have an epiphany. There are really no other substantial clues tying in the hourglass-caused degeneracy with the version number; just a bunch of really tiny clues corresponding to every change I ever made to the game.

Finally, there are two things going on in the game, and it's never made explicit that they're different. There's the curse, which was triggered by a contigency spell upon the Baron's death, and which prevents you from touching important objects like the hourglass and the wheel; and there's the degeneracy, which is caused by the hourglass (the hourglass also being set into motion by the contigency spell). I couldn't do much to differentiate between these two things because they're closely entwined and I couldn't think of a way to describe their relationship in a way that sounded like the PC's internal monologue. I had to punt and rely on inference, and it didn't really work.

Conclusions

I'm glad I wrote Degeneracy, but I'm even more glad I'm done with it. I have some ideas in a similar vein[2], but I'm holding off on them until I can come up with good frameworks and plots for them. Degeneracy works because the game world is rich enough that something is lost when it begins to fade away; empty exercises in formalism are no fun for the author or the players.


Footnotes

[0] In particular, the hourglass was at one point reimplemented from scratch. After I initially wrote it, Mike Popovic and I kept thinking of new and annoying things that a player could do with the hourglass and the piles of sand, and the hacks I'd made to handle them were incredibly buggy. I rewrote the hourglass using a primitive physics model so I could convince myself I'd gotten all the cases.

[1] Actually, originally originally the version number was shown on the status line instead of the number of moves, but this made it way too obvious, so I retroactively changed it.

[2] That vein being "secretly subverting the player's unstated assumptions", of course, not "changing the version number of the game during play".


This document (source) is part of Crummy, the webspace of Leonard Richardson (contact information). It was last modified on Thursday, April 29 2004, 20:36:38 Nowhere Standard Time and last built on Thursday, October 23 2014, 00:00:04 Nowhere Standard Time.

Crummy is © 1996-2014 Leonard Richardson. Unless otherwise noted, all text licensed under a Creative Commons License.

Document tree:

http://www.crummy.com/
software/
if/
degeneracy/
postmortem.html
Site Search: