Procedural Text Generation in IF

In the Missing Tools discussion some time ago, one of the things people mentioned wanting more of in IF was procedural text generation, which here is meant specifically as the ability to have the computer describe complex world model states or story events without having to hand-author every possible variation.

This is an area where there’s a lot to learn from work going on in academic research, but as far as I’m aware there’s relatively little communication. As I mentioned in my ICIDS writeup, James Ryan at UCSC and Dr. Boyang Li at Georgia Tech’s Entertainment Intelligence Lab are doing work on a) how to better represent a richly complicated world model and b) how to procedurally alter narrative features such as the tone of narration. One of the things we particularly don’t seem to do in hobbyist IF, perhaps for lack of resources, is experiment with large word databases such as WordNet or crowd-sourced work in particular areas like that used on Scheherazade.

Speaking for myself, I’ve also tended to stumble towards solutions in this space based on trial and error and the needs of my own projects, rather than having a strong grounding in the relevant academic work. Most of what we’ve needed — and most of what we’ve done — is pretty much work in the shallowest end of this pool.

And, of course, text generation for parser IF comes with special additional challenges, in that the player usually expects to be able to refer to any generated noun or noun phrase element; therefore if we generate a description of a thing as “blue”, the system also needs to remember how we described that object and accept the input “blue” to refer to it.

Here are the things I’m currently aware of. Unfortunately, I’m inevitably more aware of the internals of my own libraries and games than I am of other people’s work, so if I left out something cool that you did, please by all means say something in the comments: I am eager to know about it. In particular, there may be a lot I don’t know about under the hood in Kerkerkruip.

Common

Substituting text based on a variable and/or world model information. Pretty much all the major systems are capable of this. Inform also allows syntax such as “understand the color property as describing a brick” also to affect parsing to make this easier to parse.

Choosing randomly from multiple text options. Inform’s [one of]…[at random], [one of]…[as decreasingly likely options], [one of]…[sticky random], and similar features; TADS and Twine also provide this functionality. Most of the time it’s used to handle randomized atmosphere messages and NPC idles, or to diversify the responses to player behavior instead of using just one default message all the time. Counterfeit Monkey uses some randomness and cycling in default messages in order to make the narrator character Alex seem more personable and less like a computer.

At the more ambitious end, this technique can be used to build out randomized room descriptions: see the maze rooms in Hunter, In Darkness and the entirety of My Secret Hideout.

Recording and reusing user-supplied strings. Probably the most common application of this is to ask for the player’s preferred name, as exemplified in the example Identity Theft in Inform; all the major systems that I know of have some way of doing this, since it’s just collecting text and then subsequently using that variable for generation.

Ultimate Quest kind of went to extremes with this by allowing people to rename objects however they wanted, permitting them to assign their own synonyms or nicknames to things. This occasionally had an in-game value but often was used to do things like rename villain characters to JERKFACE. (The code obviously needed a few checks to make sure that players weren’t naming objects the same as some other existing object.)

Selecting singular and plural forms, or generating them. Inform and TADS both deal with this to create plurals of nouns and to make verbs agree properly; the systems have over time gotten more and more elaborate, and tied in with

Revising person, number, and tense of narration. Curveship got here first; the TADS libraries adv3 and adv3lite, and Inform’s 2014 builds all supply ways to do this as well. (And before the 2014 build, there were more clumsy ways of doing this with library message replacement libraries such as Custom Library Messages.) Because this applies to verb forms and not to nouns, it doesn’t tend to raise the same parsing challenges.

This can be useful to shift the descriptions of an entire game; Shelter From the Storm rather dramatically lets you choose your own person and tense of narration as you like. It also turns up in flashback sequences in only part of a game; I think All Hope Abandon does this, and I know Dial C for Cupcakes does. (It’s also possible to find first person, third person, and past tense games on IFDB using tags. I checked for future tense games and didn’t find any, which may reflect a genuine absence thereof.)

Semi-common

Selecting and ordering information to be narrated. The Inform libraries Complex Listing and Room Description Control; Inform’s writing a paragraph about and locale activities.

Room Description Control is the most systematic of these, and is designed to allow the author to

  • write her own rules about what room description information should be mentioned at all, which means one can write rules like “anything marked as invisible should not be mentioned” or “things on high shelves should not be mentioned” or “open objects should be mentioned”; then
  • once a list of information to report has been established, write rules controlling the order of that that report, e.g. “people should be mentioned last”; then
  • using the “write a paragraph about” activity, provide more or less specialized templates for describing particular sets of facts or objects. This means one can, say, provide a special override for the case that the statue bust is on a pedestal vs. in any other location.

One of the major points about “write a paragraph about”, both as used by RDC and in its use in vanilla Inform, is that it’s not necessarily trying to assemble a human-sounding paragraph from scratch (though Tailored Room Description does provide some attempts at that for generic cases). Instead, it’s matching an author-generated paragraph to the particular selection of salient information it needs to report, and falling back to default descriptions if it can’t find any such paragraphs available.

This is closer to the approach taken by the Prom Week engineers and James Ryan’s current work, though in practice IF often needs to rely on mix of both methods.

RDC demands a lot from the author, so it has a companion extension Tailored Room Description that exemplifies how much of the heavy listing is done (and can be used as an out of box solution, though I imagine the only reason someone would go to the trouble of using it would be if they wanted to make some major changes).

See also this article on generating physical descriptions for characters on a MUD.

Talking naturally about lists and quantifiers. The aim here is to avoid clumsy reports of world state concerning sequentially related model data, such as “The left drawer is open. The middle drawer is closed. The right drawer is closed.” The rather vaguely-named extension Assorted Text Generation mostly does one task: write phrases identifying members of a group, such as “Both of the doors are open” or “All but the white door are closed”. It includes a number of minor variations on these, and also has some ways of talking imprecisely about large numbers.

Building on this, the Automated Drawers extension is basically an excuse to use this functionality; to the best of my knowledge it has wisely been avoided by other authors, though I did use it in Counterfeit Monkey. Its function is to allow the author to say that a given desk has N drawers and that they’re vertically or horizontally arranged, and then autogenerate and auto-understand input such as “open the top drawer” or “the top drawer is open”.

Combining clauses into paragraphs. The challenge here is to take a sequence of single-clause sentence texts such as “Mary enters the room. Paul enters the room. Paul sits on the bed. A dog is on the bed.”, generated by world model information, into a more natural-sounding paragraph, using strategies such as combining sentences with shared information, but avoiding erroneous over-combination:

“Mary and Paul enter the room. Paul sits on the bed.”
“Mary enters the room. Paul enters the room and sits on the bed.”
* “Mary and Paul enter the room and sit on the bed.”

Savoir-Faire does this with its object-throwing-and-landing code: things can be thrown at other things, and the target objects might break, roll away, fall, spill, etc. In theory, liquid can pour out of a broken vase, or a marble fall out of a glass box and then roll off the shelf the box was sitting on: the possible ramifications are numerous. SF also provides mechanisms for reporting less-vital “color” information like what sound is made when one object strikes another. So to report all this, it needs to

  • assemble a list of clauses describing all the different things that happen, labeling them as instant (things that could happen at the same time as other things) or sequential (things that in themselves take time)
  • choose which if any clauses it can eliminate (if there’s lots of important stuff to report, color information may be omitted; if not, it will leave that information in to keep the report interesting)
  • generate output sentences by stitching clauses together in a way that respects their temporal information as well as trying to minimize redundancy

It’s actually even a bit more ridiculous than that, because each verb that can be the main verb of a clause was implemented as an object with special rules about how to print itself, including making alternate short, medium, or long instantiations of those clauses in order to produce stylistic variation, and doing some stuff to automatically create verb plurals where needed, and so on.

Honestly I am not sure that players noticed or cared about most of this; the point was mostly that I was enjoying the mad science of it all.

The I7 extension Approaches, meanwhile, deals with traversing large amounts of territory and putting together descriptions of all the rooms the player passes through.

Most rigorously of all, a generalized combination of reports of player actions are built into TADS 3 in order to present. I’m not an expert in how this works, however, since TADS 3 has not been my main language of choice. Games like Return to Ditch Day demonstrate the effects, though.

Unusual

Adjective selection for complex objects. The last category slightly gets into this one: sometimes we want to apply identifying vocabulary to a particular object based on multiple aspects of the world model; an NPC doesn’t just have the property “angry”, for instance, but can be described as “apoplectic” if she has both the mood angry and the personality profile domineering.

Noisy Cricket is an Inform example that constructs a name for an alcoholic beverage depending on the mix of liquids included in that beverage; otherwise I don’t know of a large number of things that do this much in practice. In that case, it’s assumed that player actions will directly prompt changes in the object name, which may not always be the case. If it’s necessary to respond to world model changes that might be happening for multiple reasons, one might calculate and reassign descriptors to objects each turn in order to keep them up to date with the world model.

Filtering output to add verbal tics and similar features. These are filters that are added after the fact once the text is already fully composed in order to add surface-level effects or defects. Versu has a drunkenness filter built in which automatically slurs text output by a character that registers as drunk. Curveship’s examples include an um-inserter to make characters seem to hesitate.

*

Other obvious examples I’m not thinking of?

22 thoughts on “Procedural Text Generation in IF”

  1. Haven’t seen it in an IF but I wrote a program that generates astrological forecast (Horoscope a Minute on Google Play). It was basically generator of plausible text with no specific meaning parodying vague “open to interpretation” phrases.

    The same technique can be used to generate some useless filler text. E.g. in a story spanning a week the hero may read newspaper where important stuff is hidden among procedurally generated titles. Or some character can write obviously bad nonsensical poetry. Or alien character may speak random words organized in something that looks like sentences in foreign language.

    1. Oh, yeah, good point: comedy or semi-comedy randomness turns up moderately often. Curses has randomized song selections playing on Aunt Jemima’s radio; Counterfeit Monkey has several objects (the film reel and the tale most prominently) that can put out silly little auto-generated stories of a standard format; probably loads of other examples I’m not thinking of at the moment.

  2. “One of the things we particularly don’t seem to do in hobbyist IF, perhaps for lack of resources”

    Or, perhaps, because you put a higher priority on the quality of the results vs the methods you used, whereas in (technical) academia it’s more the inverse? And I don’t mean to imply either one is less worthwhile than the other. It just seems that from an incentive point of view, that’s how things end up. Also, whenever I have a design problem, I can design it away as often as I can program it away.

  3. I’ve only used a couple of these models. _Hunter in Darkness_, as you noted, is the common one. That’s built on a substutition engine that I’ve ported to several languages now. A Javascript version is here: http://eblong.com/zarf/mutagen/ . _My Secret Hideout_ recreates it in ObjC; Seltani includes a Python version.

    The engine is based on a seed variable, so a given output text can be replicated by repeating the seed. It supports a bunch of operations: sequences, randomly-selected choices, weighted randomness, choices that are random but fixed within a particular output text. It also supports chaining phrases together with various punctuation (comma, semicolon, period, paragraph break) and adjusts capitalization to match. More advanced options (not in the JS version, but used in _Secret Hideout_ and Seltani) let you define an invisible state variable and then make choices later in the output text that depend on the variable.

    _Hadean Lands_ does a little bit of combining clauses into paragraphs. For example: “In the heat, two wooden splints catch fire; the lead rod melts; the flask of saline shatters; the bundle of paper and the rotor card burn up.” But I didn’t go nearly as far as _Savoir Faire_ does.

    (The game can generate lists like “You make your way to ROOM. You take the X. You take the Y. You take the Z. You brew a bottle of F potion.” I had originally planned to combine these into lists and sentences, omitting the least important entries. But I didn’t have time to do that — and anyhow, it probably would have obscured important information from the player.)

  4. Choosing randomly from multiple text options. Inform’s [one of]…[at random], [one of]…[as decreasingly likely options], [one of]…[sticky random], and similar features; TADS and Twine also provide this functionality. Most of the time it’s used to handle randomized atmosphere messages and NPC idles, or to diversify the responses to player behavior instead of using just one default message all the time

    It’s also used to emphasize interchangeability. Olivia’s Orphanorium generates moderately complicated descriptions of each orphan’s parents (figuring out adjective order is fun), which provides some flavour but affects the game not at all: part of the idea was the Victorian tendency – particularly in scientific realms – to equate one oppressed class with another (women are like children; criminals are atavistic; the categories of mental disability correspond to racial categories).

    Begscape does something similar with its fantasy cities: the awkwardness of the generated text (‘there are many sewers and assassinations here’) lends emphasis to the sense that these details, while perhaps pretty, are irrelevant.

    So I suppose the thing that should not go unmentioned is that among the most straightforward effects of procedurally-generated text is snark or comedy – a function for which not getting it perfectly right is a distinct advantage. (The obvious thing to say here is that the most-recognised features of the fictional world and culture of Dwarf Fortress are to do with things that highlight the artificiality-despite-complexity of the program in bizarre and funny ways.)

  5. In Renga in Blue if a word is unrecognized the game will remember it and occasionally slip it in as part of one of the poems. It’s rare enough that the player might not realize they’re being echoed.

  6. Seeing as you called out Kerkerkruip, was there something in particular you were thinking about it? I don’t think we really do much procedural text generation, just a little bit of [at random] here and there. We don’t move out of the ‘common’ territory.

      1. Some properties like size or temperature will get printed if they get changed, but the order of the terms is just the order of the printing the name of rulebook. The same with combat reports, it just goes through a rulebook. The only massaging of the output text it does is checking whether commas are needed or whether an equals sign can be left out.

        I’d love for the game to have more PTG! But it doesn’t at the moment.

      2. if we were actually creating prose reports of everything that goes on in the combat, instead of relying on partly numerical output, I’m sure we’d need to do a lot more. But our current system — while certainly not trivial in terms of the number of its parts — is really just a case of good rule ordering.

    1. Kerkerkruip is a roguelike, so the repeating of text in combat situations improves readability. Each time a blow is given the reader knows where to look quickly to get the number and the key words that describes the success or the failure of a blow. So, I don’t think it is a good idea to add procedural text heavy randomized so the player looses immediacy in the readings of her actions.

    1. Yes, that came out just after I’d finished writing and scheduling this article. It’s well worth a look, as are Chris’s notes here which talk about both the ProcJam project and the IF Comp submission Origins.

      The idea of autogenerating Twine also intrigues me because it suggests the possibility of building richer world models for choice-based fiction than are currently typical, retaining the player accessibility of Twine, but avoiding the authorial burden of handling that model as a bunch of Twine variables. Instead some other level of language could be used to specify the world model and its behavior, and then compiled down into something in which specific links were generated.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: