Disclaimer: I Have No Idea
This is a somewhat rambly post where I’m trying out some ideas that I’m currently halfway through implementing…
Before the player can mess with character memories, they need…well…memories.
This is done currently by starting off with some number of characters, and letting them each randomly choose an event to add to their timeline, whose pre-conditions they satisfy.
(It might be confusing right now that I call them events instead of actions, but hang with me.)
Here’s an event:
Not so bad, right? Here are the fields:
id: unique ID
timeSteps: how long this event takes
surfaceText: the Expressionist grammar to generate surface text for this event, depending on who’s asking.
preconditions: simple blackboard-style state variables that must be valid
effects: changes applied to every character in cast after event runs
What About Cast?
Ok this is the complicated bit. Cast can be any number of characters. Each character has their own “characterArgs” (preconditions for filling the role) and a memoryGrammarBank.
So…Events are tree structures. For each character in cast, for the number of timesteps, it iterates over their memoryGrammarBanks and chooses one to process.
What are those things in memoryGrammarBanks? They’re other events. When trying to pick one from a memoryGrammarBank, it:
1. Favors Events whose timesteps are largest while still less than or equal to the remaining time of the parent event
2. Picks only Events whose preconditions hold
3. Picks only Events who can be cast
4. Favors casting characters already cast for parent events, and failing that, characters who are lower-status than the parent character, and who have similar Substrate (that’s the game ontology) domains to the first character
This means that, dramatically, events are written with the first cast slot as the most important. And it means that characters that are subordinate to, but of the same domain as the main character, or more likely to get drawn into their event orbit.
We build out the tree depth-first, which means we just keep iterating down until we get to a base grammar that doesn’t have any further grammars in the castSlot, just in the surfaceText. We then generate that surface text by running it through Expressionist, add the event to the character’s timeline, process the effects, and pop back up to the parent to expand out the next cast call.
What’s Expressionist Surface Text?
Expressionist is Jame Ryan’s system for generating surface text by iterating through attribute grammars. The cool thing about it is that, unlike vanilla grammar structures, each processed rule applies a metadata tag to the section it generates. These tags can be anything, from “cat” to “effect: <char1> anger +1”, which brings me to the cool part:
In generating the surface text from production rules carefully tagged at author-time, we can get emergent simulation behavior.
We do this so that we get the authoring benefit of writing combinatorial surface text, and the effects that fall out from that via meta-tagging can update the state accordingly.
So that’s how we do timestep 1!
What About the Rest of the Timesteps?
We don’t expand out the rest of the tree, because after a character’s timestep is run, the effects are applied. This could potentially change the state such that casting decisions made from an earlier state aren’t valid.
The rest of the tree, as it applies to each character, is called their Geas. When the next timestep is processed, we repeat the same process, for the events that haven’t been rendered out yet. If it turns out we can’t process the event due to the preconditions no longer being valid, we bump up the tree to the next level up, and see if we can process from there.
If none of the events are valid for the character, then we start a new root tree for them.
The cool thing about this event structure, I think, is that it means we can conceivably group events together under loose category events (happyWalking and sadWalking are in the memoryGrammarBank for walking) and chain them for long timesteps. You could have one giant event called “tragicLife” that has 1000 timesteps, composed of 5 100-timestep “normalLife” events, followed by 3 100-timestep”wonderfulLife” events, followed by 1 100-timestep “terribleLife”, and 1 100-timestep “disastrousLife”. These loose categories would have very large memoryGrammarBanks, but would allow the author to specify a general structure this character’s life should take.
Remember when I said you could generate surface text with tags like “effect: <char1> anger +1”? Because we don’t need to worry about acting out the characters’ actions in the simulation, we can use this to run game actions, so that we can update the simulation as the surface text describing what happens is generated.
For this game, we want whenever possible to tie things back to the Substrate. So for a surface text production “she climbed the tree to marvel at the mockingbird’s nest” the production rule for generating “climbed” has the tags
“prec: <char1> climb > 5” and
“effect: <char1> climb +1”.
This means that when we ask Expressionist to give us an expansion, we can say “don’t give us an expansion that has a precondition tag for stats higher than ours”. This also means that, because this production is returned, the character in cast slot 1 was “given” more affinity for climbing when this action completed. We could also imagine “tree” having a tag of “effect: <char1> tree +1”. This means the character has more “treeness”, and for things involving trees in the future, they may do better with them, or talk about them more in conversation, or find themselves more wary of fire, etc. Those things are determined by the connections between the concepts in the Substrate.
We could also specify who did the effect. For surface text like “Fivetoes took her music box” we could use an effect “effect: <char1> takeItem music box causedBy <char2>” which is (confusingly) char1 losing the music box as an effect done to them by char2.
These effect tags are returned from Expressionist with placeholders like <char1> etc, and those are replaced by the system, and processed into state updates. So the string processed by the system would read something like “effect: starEye takeItem musicBox5 causedBy fivetoes” which would run the function takeItem(starEye, musicBox5, fivetoes)
If we look at skilltrees as conceptual affinities for actions done to entities (potentially with other entities) it seems like we can get some pretty interesting fertile ground, especially we can leverage hierarchies through linking.
Thus, I may be able to avoid a dragon’s fire if I have experience with either dodging, fire, or dragons. Or…I may be able to avoid it because I am good at dancing, which has the connection facilitatedBy to agility, and dodging also has a connection facilitatedBy to agility. But if I want to change that, all I need to do is break the connection between dodge and agility, and move it to “daring” or “reflexes”, and the rest falls out from there.