Introducing Herodotus: Procedurally Fathering History
The challenge
Games based on static content have a number of intrinsic problems:
- Choice is illusory. Every choice presented to the player either results in no meaningful branch, or one of a pre-selected, pre-planned number of meaningful branches, so:
- Content is impersonal. There is no significant way to customize the game to the player. Automatically scaling difficulty is about as far as you can go with static content, because:
- New content is expensive. Since each branch is pre-determined, branches must all be made individually. This is inefficient for content creators, which means that:
- Content is finite and fairly sparse. Eventually players will run out of branches to traverse. Therefore:
We should look towards implementing more dynamic games.
Games based on dynamic content can solve the above problems - not just on their initial release, but also incrementally with subscription or microtransaction-based content additions. Here are just a few ways that a move towards truly interactive narratives can improve gameplay:
- Choices can be significant and varied. If the atom of choice isn't "which branch of five to take", but "which state the world will be left in", then all gameplay can become like simulation gameplay: really open-ended, really consistent, really expansive. And the actions the player takes will be important. If the player assassinates the opposing faction's leader, a civil war can break out. The death of a monarch is only one possible way to enter a "civil war" scenario, and a "civil war" is only one of the possible outcomes. The player could just as easily have had a nice tea with the opposing leader and signed a truce!
- Player actions can lead to customized experiences. If the player shows a tendency to kill Green Blobs instead of Pink Blobs, the game can increase or reduce the respawn rate of Green Blobs; if the player is constantly being killed by Dark Horsemen, the game can make that species weaker. If it's an online game, the background chosen for his character could be integrated into the persistent world's history, and the "Orphan of noble birth" could find his family's heirloom rings, swords, and other finery, or even be granted his title again!
- New content is still expensive, but it's worth a lot more! Going back to the civil war example: A system like this makes it easier to add more kinds of choices. Rather than just one time at which the civil war could start, the player can choose when to trigger it, or, in fact, whether to trigger it at all. By making the parameters semantic with respect to the fiction, rather than static with respect to a bunch of game triggers, new combinations of existing content make every piece of added content automatically worth more. Since the available paths through a dynamic setting such as this can increase combinatorially with developer effort, as opposed to linearly, I propose the following challenge:
Design a unified system for expressing the preconditions, invariants, and postconditions of arbitrary incidents. It will be called Herodotus, after the father of history.
The theory
By expressing "things which can occur" as incidents to placed in a historical timeline, Herodotus provides a unified means of modifying the game world in response to player, NPC, or intrinsic simulation actions("incident sources"). Incident sources can trigger wholly self-contained incidents according to deterministic or even random criteria, and Herodotus can track the changes to the environment implied by each incident. In fact, queries can be made at any time period, even in the "past".
Herodotus defines three primary concepts: Fluents, Incidents, and Histories. A Fluent is a function of a history, a time, and arbitrary additional parameters, and is associated with one or more "fluent types" and "contexts". In other words, it's "Something which is true for some context at a given time". Fluents are set in groups by Incidents, which also express preconditions and invariants. A History is a series of Incidents, allowing queries into the Fluents which are active at a given time. Queries can be made on fluent types and contexts, and the results that are returned depend on the Fluents involved. The following few sections describe these concepts in greater detail, but those looking for examples can skip ahead.
Fluents
A Fluent is a value parameterized by at least history and time, matching some set of "fluent types" and "contexts". Fluent types represent a category of Fluents. For example, there might be fluent types such as nobles, trade-routes, quantity-of-food, friendliness, neighbors, and so on. The context of a Fluent depends, in some sense, on the type. The context of a fluent of type wars would include at least the information to identify the War involved. A fluent establishing a trade route would have, as its context, the nations and goods involved in the route, perhaps encapsulated in a "Trade" object. In other words, the context is the data required for a Fluent to perform its calculations. When Fluents are queried, the query consists of a set of fluent types and contexts, along with a history and time. All Fluents matching any of the involved fluent types are used if their contexts also appear in the context list, and the Incident triggering them occurred after the given time. Empty lists match "all". One final note on the static properties of Fluents: In Herodotus, Fluents don't have access merely to their own contexts, but also the contexts of their Incidents. This is mainly for convenience, and has no significant impact on the semantics.
Fluents also take dynamic parameters, which consist at least of history, h, and time, t. While a population Fluent might only be parameterized by time for an exponential model, a height fluent for a heightmap-driven terrain could include (x,y) parameters. This is a nice way to reduce the number of Fluents, where applicable. Imagine if each entry in a heightmap had to be embodied by a unique Fluent! In this fashion, not only is efficiency obtained, but the fidelity is improved. Another clever use of t is for a Fluent which "lies latent" until or after a given time. For instance, a fluent may be defined piecewise: Doing nothing for the first 10 time units after starting, then increasing linearly for 100 time units, then increasing exponentially for the next 10 time units, then immediately going to 0. As for the history, h, this is provided so that Fluents can calculate values based on Fluents that match some other criteria. This is an advanced technique which opens up the possibility of infinite loops, so it shouldn't be used except in extreme cases.
When the values of multiple Fluents are to be combined, their respective StackModes are used to combine their results. Example StackModes include simple replacement, numerical addition and subtraction, list appending and removal, and so on. StackModes have access not just to the value of the Fluent which employs them, but also the value of the rest of the stack "underneath", that is, earlier in time. The StackMode can use that value or discard it. A Fluent which relies on previous values to calculate its own might even do most of its heavy lifting through its StackMode. Taking a population model as an example, a Fluent which does a mathematical operation on the "base population" defined by the previously occurring Fluents might return, as its value, the parameters for that operation. Its custom StackMode would then take those parameters and apply the operation to the "prior value", returning the result.
Fluents are the unit of information stored in a History.
Incidents
An Incident is a sort of container for Fluents, used by the application to group and configure related Fluents caused "at once" in time. Incidents also allow for preconditions, which are functions of a History, and invariants, which are functions of a History and a time. Incidents can also terminate previously applied Fluents, and they are further responsible for expressing their own inverses. For instance, the inverse fluent of an Incident which established a trade route between two nations might be the dissolution of that route(the invariant being that there is an accessible land route between those nations; a flood or earthquake might end the contract).
Incidents are created by the application. An application might have a "PersonBorn" incident which sets various fluents related to the birth of a human, and which is configured with properties such as the person's name, parentage, and so on. The application could create several PersonBorn Incidents and trigger them all at various times, choosing which to trigger based on the ages and marital status of the parents and other concerns.
Incidents are the unit of application influence over a History.
Histories
In a sense, a Herodotus History provides a fluent database - it becomes the data store against which the application queries. For instance, questions like "Are there any mountains nearby?" or "What is the height above sea level at (42° 40'N, 73° 45'W)?" can be asked of a History, provided that the Fluents to supply those data have been defined, and that Incidents have triggered them. A History maintains a collection of Incidents which have occurred within it, and verifies that new Incidents can occur according to their prerequisites. When a new Incident is inserted, the History goes through the invariants of prior events and the prerequisites of future events to ensure consistency. If an invariant is violated, the new Incident also sets the violated Incident's inverse fluents.
Herodotus is intentionally very slim. All of the actual shape of the simulation - which Incidents are defined, when Incidents can occur, the resolution of the simulation, and other concerns are left out on purpose so that Herodotus can be as general as possible. It's quite feasible to use Herodotus as a completely static storytelling tool, merely making use of its ability to maintain a story's consistency, or to provide a unified way to check triggers and quantities. It's also possible to use multiple Herodotus Histories, and eventually collapse such a timeline into a single event. For instance, a new History might be created for a random battle in an RPG. The initial Incidents would set up the participants in the battle; subsequent Incidents could be placed for each action chosen by the player or monster. Player and monster stats would be Fluents in this History. Once the battle is over, the History can be analyzed by the application, and based on how well it went, a simpler Incident could be placed in the "real" History. If desired, this Incident could even refer to that History! In this way, the simulation can reach an arbitrary level of detail, yet remain reasonably efficient at each level.
Histories are the distinct stores maintained by Herodotus.
An example
For instance, here is a series of Herodotus incidents:
- IncidentType=PersonBorn Person=P730109 Father=P13024 Mother=P12049 Time=1370
- IncidentType=PersonTrained Person=P730109 Teacher=P10123 Specialty=S7 Time=1383
- IncidentType=WarStarted War=W7 Attacker=N1 Defender=N2 Time=1390
- IncidentType=PersonJoinsArmy Person=P730109 Army=A7 Reason=R4 Rank=K7 Unit=U512 Time=1391
- IncidentType=BattleWonByDefender Battle=B18 War=W7 Place=C197 AttackingUnit=U213 DefendingUnit=U512 Time=1396
- IncidentType=PersonPromoted Person=P730109 Rank=K8 Time=1399
In 1370, Richard Richardson III was born to the well-regarded painter High Duke Richard Richardson II and High Duchess Rachael Greaves-Richardson. At age thirteen, he undertook extensive training in tactics under Swordmaster Silvas. A year after the start of the War of the Black Cyprus in 1390, Richardson joined the Queen's Army at the rank of Commander due to his father's influence, and was assigned to the Ninth Golden Sabre Regiment. When the First Rofon Hills Battle took place in 1396, Richardson's unit defended against the onslaught, which, among other exploits, granted him a promotion to Captain in 1399.
To attain the above goal, all that's required of the application, besides the simulation itself, are random name generators, texts associated with different incident types, and a slightly clever way to display a list of incidents. As Perko points out, generating text was never the hard part. But what if we want to generate gameplay with this? Well, that's easy enough, once we grok the following: Any of those incidents could have been triggered by the player. Let's see that sequence again, but with some choice points interleaved:
- Goddess of Creation:"Please choose a name, sex, and background."
- IncidentType=PersonBorn Person=P730109 Father=P13024 Mother=P12049 Time=1370
- Player: Richardson III walked to the Registry of Learning, and swore to devote five years to the study of tactics under a great master.
- IncidentType=PersonTrained Person=P730109 Teacher=P10123 Specialty=S7 Time=1383
- National News: A war erupts between Eyland and Targon.
- IncidentType=WarStarted War=W7 Attacker=N1 Defender=N2 Time=1390
- Player: Richardson III asks his father if he can join the army. His father requires that Richardson III prove his resolve by helping him paint a scene of a great past battle, while explaining the tactics of the incident.
- IncidentType=PersonJoinsArmy Person=P730109 Army=A7 Reason=R4 Rank=K7 Unit=U512 Time=1391
- Player: A battle breaks out near the military school. Richardson III manages to best the commander of the rival unit.
- IncidentType=BattleWonByDefender Battle=B18 War=W7 Place=C197 AttackingUnit=U213 DefendingUnit=U512 Time=1396
- National News: Richardson III is promoted.
- IncidentType=PersonPromoted Person=P730109 Rank=K8 Time=1399
The choice or action points there may seem magical, but I think they can be easily framed in terms of existing game quantities, and are not necessarily more complex than a simulation game such as Civilization or SimCity. By expressing more and more quantities in terms of time-sensitive Fluents in a Herodotus timeline, incident generators can access massive bunches of data. For instance, the trial proposed by Richardson II took into account his own expertise, his son's trained skills, and his son's goal. So, there are two vectors for improving a Herodotus simulation: Increase the number of Incidents, and increase the variety of the Incident Generators, which can present choices to the player or to the NPCs.
Choices and Incident Generators
The first claim in the above paragraph bears some justification. In the parlance of Herodotus simulations, a Choice is any selection of one or more Incidents. Note that Choice is not a concept intrinsic to Herodotus, but a feature of games or simulations written to use it. Choices, in application logic, refer to data from one or more Histories, and might be driven by an underlying simulation ("If the strength of nation A is greater than the strength of nation B, and if A and B are unfriendly, A should start a war with B; otherwise, A should try diplomacy"), player choice followed by system response ("Hit the Diplomat with a Stick"), or even direct player intervention (as in a "choose your own adventure" game). This is what is meant by saying that such Choices are framed in terms of existing game quantities, and that they are no more complex than a standard simulation. Designing simulations is still hard! Herodotus just provides a consistent way to phrase simulations, so that some of the bookkeeping can be handled in a unified and automatic fashion.
An agent which resolves Choices to Incidents is called an Incident Generator, or Generator for short. Any number of Generators could run concurrently, even on a single History, and Generators can be arbitrarily complex.
The next step
This article intentionally ignores such details as how Incidents and Fluents are defined syntactically, as well as the particulars of this implementation of Herodotus. This is to keep me honest and focused on the real dynamic properties of this system, as opposed to getting caught up in how cool it is to geek around with mathematical representations. In the coming days, I'll start talking about how these ideas are expressed in the current implementation of Herodotus as I go through some sample simulations, which I'm going to call "Inquiries".
The first Inquiry will be a simple terrain generator to show how even incidents in geological time can "count" as historical occurrences. I'll also be showing an example of how an application's structure might, rather than access and store data directly, make use of various objects wrapping a single history to provide simple, time-centric access to data.
Finally, this and the following articles can be considered a "Request for Comment". If you have any questions about Herodotus, or suggestions on extending this essay to provide better information, please email me.
Thanks to Melissa Houlroyd, Rob Rix, and Andrew Eaton for their input on constructing this essay.
Additional Herodotus essays are visible in the sidebar at the right.
Joe Osborn 2008-01-21
0 Comments:
Post a Comment
<< Home