Introducing Herodotus: Procedurally Fathering History

The challenge

Games based on static content have a number of intrinsic problems:

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:

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
And here is how a textual game might interpret it:

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.

Labels: ,

Joe Osborn 2008-01-21

0 Comments:

Post a Comment

<< Home