Thesis Log: Constrain yourself
Brain dump time. A new system is being put into place which I am finding to be very promising, based around the idea of constraints. But I'm getting a bit ahead of myself here. First, let's talk about what we want for a story.
At its core, a story is about agents and objects (at least, as far as have been modelled so far). Agents are living, thinking things—in our case, NPCs— and objects are inanimate things they manipulate. Now, when creating a story, it goes along with it that we have to either match or create agents and objects to go with our story, and this is where constraints come in.
Do note, here, that matching and creating agents are two entirely separate problems, one of which (matching) is generally easier than the other. For the moment, assume that both operations are possible.
Okay, so, let's say we have a story that demands that there be a villain. We can then say formally that we require the story to contain an agent with the role "villain". To further flesh him out we may be interested in making sure he has other properties as well... let's say we've decided that whoever he is, he should be a wizard of some sort (note that this is a pretty trivial example, and that we could think of all kinds of other more interesting things than that); thus, we decide to add the constraint "is a wizard".
Purely implemention-wise, this is done through a simple mapping dictionary where aspects of an agent are divided up along keywords. For instance, the above would be modelled in python as {"profession" : (Wizard,)}. It should be noted, also, that the folding procedures mentioned below do not assign any inherent meaning to particular keywords; only the matching/creating algorithms care about what they actually .
Okay, again we're getting ahead of ourselves. Noting again that what have is a desire to have a villain who is a wizard, we now register this desire (called a request). Our story has now signalled that we want this particular agent realized at some stage; but right now, he exists only in potentia, a description rather than an actual entity. If you will, think of him as a functional monad or a continuation.
At a later stage, but before the story becomes available to the player, we process all currently pending requests for agents, and realize them. What this means is simply that we search our database of NPCs that we know to exist in the world and find one that matches the constraints we have specified. If we cannot find any, then either our story is impossible and should be discarded, or we can allow ourselves to create a new one (that is, if we have workable creation algorithm).
Once an agent is realized, it's tied to an actual living NPC somewhere in the world, and we can proceed to set up conversation hooks and triggers that let the player actually experience the story.
More about constraints: Each constraint is actually a set of acceptable properties. For instance, consider an agent having the constraints {"profession" : (Wizard, Priest, Shaman), "karma" : ("negative","neutral")}. The proper way to interpret this set of constraints is that what we want is an agent who is either a wizard, priest or shaman, and has either negative or neutral karma.
The basic idea here is that we generally don't care exactly what we get, as long as we are within our known constraints. That also means that any properties not listed among an agent's constraints are interpreted as being non-essential, and the matching/creation algorithms ignore such properties entirely. In the above example, for instance, properties like the agent being a particular level or having certain skills is interpreted as something we don't care about, so you may get anything at all as far as they are concerned.
One more thing should be mentioned: Agent folding. It is often so that we want a particular villain, say, to remain the villain for the entirety of the story. We don't want him to suddenly vanish from the stage and be replaced with a brand new one. Instead, what we do is we "fold" requests for agents together.
Imagine that an early token (node, story section) requests a villain with the constraints {"profession":(Wizard, Priest, Shaman), "karma":("negative","neutral")}, as above. Next, another section requires a villain with these constraints: {"profession":(Wizard, Shaman), "karma":("negative",)}.
We note that the two constraints could be merged. A villain that is simply a Wizard with negative karma would fulfill both constraints just fine. So, what we do is we fold the requests together into one request, reducing its constraints to the intersection of both. The result is a single agent-in-potentia with the constraints {"profession":(Wizard, Shaman), "karma":("negative",)}, and when realized, both segments of the story will receive the same actual NPC. (Side note: It's also possible to explicitly ask for a brand new agent if desired, and not perform folding.)
Finally, in the end, a lot of the quality of the results we get from this method depend on the matching/creation algorithms (which are, by the way, passed the same set of constraints as the agents they are realizing), but I have great hopes for all this. Stay tuned.
Comments
The karma system is a touch of genius. If implemented well, it could be the cornerstone of a living, evolving world.