Argax Project

Node Status: COMPLETE

Reincorporation as Scene Selection Criterion

The "best choice" of the next scene involves much more than just the amount of material it reincorporates. This was reaffirmed a number of times during the development of Demeter. The design of Marlinspike evolved slightly during this implementation in an attempt to respond to some of these deficiencies.

Failure: Immediate Reactions

The first failure of Marlinspike's reincorporation-based selection process was in failing to provide immediate NPC reactions. The original Marlinspike scene selection criteria1 included the import of the current event as a bonus if the selected scene would reincorporate it. This effectively provided a bonus to scenes that continued the current action, and that bonus would be greater if the current action were particularly significant. This was intended to handle NPC reactions, which were originally designed to play like any other scene.

However, then situations like this would arise: Imagine the PC is currently in a long conversation with a number of NPCs, and then the PC suddenly slaps one of the NPCs. Suppose at least two scenes could then play: one that continues the conversation or another that has the NPC reply to the slap. If the conversation is long enough, the material reincorporated by continuing the conversation would overwhelm the bonus of immediately replying to the slap. So the slap would be completely ignored and conversation would continue.

Yet ignoring the slap--even if it might be replied to in some way later in the story--is not a valid option! It has to be acknowledged immediately, even if only in passing. Otherwise, the NPCs become completely unbelievable. Also, the player loses a high degree of world-level agency, since many interactions with NPCs would have no apparent effect.

Thus the NPCs_React scene evolved to fill two roles. The first was to coordinate NPC reactions, as described previously. But the second was to provide immediate reactions. This was done by checking if NPCs_React can play at the beginning of nearly every scene. If it can play and is responding to a current action of high import, most scenes will then choose not to play. If NPCs_React can play but in response to an action of low import, then the scene will first play NPCs_React as a component and then proceed with its own content, possibly with a small amount of bridge material.

For example, let's reconsider the earlier "conversational slap" example. Because it now checks NPCs_React first, the scene that continues the conversation would no longer be able to play in this situation because NPCs_React is responding to a significant action. Instead, the conversation would stop while the NPCs express outrage at the PC's behavior. On the other hand, if the PC had offered some food to one of the NPCs instead of slapping him, then NPCs_React would be able to play in response to a low import event. In this case, the conversation-continuing scene would still be able to play. But it would first include the results of NPCs_React--having the NPC accept the PC's proffered gift--before continuing the conversation. It could even include a bridge statement of some sort, such as having one of the other NPCs mention that the PC should pay attention closer to the conversation.

It is important to note that this change improved the responsiveness to player actions by ensuring they are reincorporated immediately. Because this can often happen as part of a scene that then continues the story, this effectively pulls what the player has done into the story line. But the difference from the previous design is that this reincorporation work is now being done by the scenes themselves deferring to each other, rather than through some mechanism of Marlinspike.2 This is less than ideal.

Given this superior method of responding to user actions in a timely manner, I dropped the import of the current event from Marlinspike's scene selection algorithm.

Failure: Advancing the Story

Reactions aside, another irritation as an author was that Marlinspike would frequently fail to select the "best" next scene from those available. In short, Marlinspike was working correctly, but there was more to consider than just reincorporation.

An example from Demeter: Suppose that the PC is a member of the GoParty exploring the Zeppelin for signs of what happened to the crew. The PC leads the party into a new room. On the floor of this room is the bloody corpse of a crew member and a stout hammer. One of the NPCs in the party has the plan of arming himself. Three scenes are then possible at this point. One of the NPCs could exclaim at the sight of a dead body. The NPC that wants a weapon could pick up the stout hammer. Or one of the NPCs could lead the group from the room.

As an author, I feel that the NPCs should acknowledge the corpse first, then arm themselves, and finally leave. However, in an early version of Demeter, they would simply leave. This is because leaving continued the thread of their ongoing exploration--which was already fairly long. Noticing the corpse or picking up the hammer, on the other hand, didn't reincorporate any events that had gone before, other than perhaps the Captain's message at the beginning of the story. Since the GoParty exploration thread ultimately stemmed from that same event, the ongoing exploration still had greater weight.

Similar situations occur at other points in the story. Upon finding the revenant, the GoParty continues their exploration. Upon being attacked by the revenant during the middle of the night, the NPCs resume an earlier interrupted conversation.

In all these situations, there is an urgency to respond to current conditions or else a need to advance the story. Selecting the next scene by only looking at what has happened previously cannot handle these situations.

In the initial Marlinspike design, when scenes were being selected to play and tied for the highest score, Marlinspike broke the tie by taking the scene that was last added to the scene manager. I began to "abuse" this behavior by loading scenes in a particular order in order to prioritize certain scenes in the case of ties. But this was a brittle, error-prone approach. And sometimes scenes would differ by only one point and inferior scenes would still get chosen. So I decided to make this feature of author-preference explicit. Import--which is essentially a measure of how important it is to reincorporate an event after it has played--was not useful here as a means to break the near-ties. So I added an imperative measure to scenes and to the scene selection process.

Imperative is admittedly a very vague concept that should be examined more closely in future work. But practically-speaking it allows me as author to grant a little bonus to certain scenes when their selection scores would otherwise be very similar. However, because imperative is only part of the scene selection score, it can be overruled by the system in cases where one scene is clearly a better choice based on the normal reincorporation rules.3

Weakness: Authorial Burden

Authoring Marlinspike scenes is not an easy task. Their monolithic play functions can become twisted logic puzzles of complex conditional tests needed to handle myriad contexts and situations. But even authoring the canPlay functions is not simple. Choosing which events a scene will reincorporate also determines how it will be scored and selected. This selection of scenes will then determine the soundness of the resulting story structure.

So the first burden on the author is ensure that there are sufficient scenes to reply to all possible significant player actions in a variety of contexts.

The next burden is explicitly listing the preconditions for each scene. This includes various world requirements, such as whether the PC is currently consciousness, that NPCs are present and capable of acting, that certain objects are present, that certain doors are unlocked and open, or that certain locations are logically reachable from the current one. Failing to explicitly list each precondition can lead to program errors and crashes when implicit preconditions or assumed states are not met in some fringe situation.

In addition, under the current design, every middle scene is required to have at least one event prerequisite so that it extends some part of the story. Sometimes this means world-level requirements become story-level requirements. For example, encountering the revenant for the first time reincorporates traveling to that location. However, using such events as preconditions can occasionally provide surprising scene selection choices--such as when arriving at a location is also part of a hefty GoParty exploration thread.

Just as significant player actions need to be sufficiently reincorporated by other scenes, scenes must also remember to reincorporate earlier significant scenes. If this doesn't happen then the resulting internal story structure is not always clean.

Here is an example of this from Demeter: When a GoParty returns to the passenger gondola, it will usually report what it experienced in the Zeppelin during its exploration. GoParty_Returns requires as a pre-requisite that the GoParty just moved into the gondola stairwell from outside the passenger gondola.

Thread: Moves_Along -> Moves_Along -> Returns
Story thread resulting from GoParty returning to gondola and reporting.

However, sometimes the GoParty comes back to find the passenger gondola locked from the inside. If someone knocks on the hatch, those inside the passenger gondola must decide whether to open the hatch and let the GoParty back in. Depending on the NPCs' states at this time, they can choose to betray the GoParty by refusing to open the hatch. Since this is a fairly significant event, the scene has an import high enough that it will start its own thread.

Extra Thread: MANIPULATE -> Reentry
Thread leading to GoParty_Rentry is left hanging.

This requires that the GoParty_Reentry scene be explicitly reincorporated by the end of the story in order for the resulting story structure to be unified. However, GoParty_Returns did not originally refer back to GoParty_Reentry, which meant GoParty_Reentry was often let hanging. This is very hard to notice in the game output because, to the audience, it is implicitly included in the flow of events: the GoParty came back, knocked, were let in, and gave a report. But it falls upon the author to make sure that these same implicit connections between events are also explicitly modelled in the system's view of the story.

Therefore, I had to add a hook from GoParty_Returns to GoParty_Reentry. To make this hook explicit in the resulting output, I also had to have a returning NPC thank the opener of the hatch before reporting.

Thread: Reentry -> Returns
Hooking GoParty_Reentry back into the GoParty_Returns thread.

Due primarily to time constraints, there are number of similar situations left like this in Demeter. This means that most stories, even with a willing player, do not result in only a single story thread by the end of the game. There will be a handful of extra threads--often with only one or two events of unique un-reincorporated material each. This sort of story structure could be cleaned up with a bit more tedious effort by the author.

Success: Explicit Story Structure

Due to problems like the one just described, the system story structure may not always perfectly match the audience's conception of the story. However, such a detailed, explicit story structure is still one of major strengths of Marlinspike.

First of all, through the process of casting and recasting, the system is capable of a detailed view of the various components and effects of different events. For example, consider the Demeter view of one action and its subsequent scene: The player shoves Count Vladescu again during an ongoing conversation.

First, here is a screenshot of the resulting output:

The Count and Elijah are made, and then John Winters resumes the discussion.
The output resulting from pushing Count Vladescu.

The following is the system view of this same turn.

|- 11=MEvent_76(HARASS<3> [Push], Count Dragos Vladescu, _, Dining Room, {-8}) <5>
|-|- 11.0=MEvent_77(DEFY<5>, Count Dragos Vladescu, _, Dining Room, {-10})[reinc:1]>[8.0.0]
|-|- 11.1=MEvent_78(AFFRONT<3>, Mrs Irene Winters, MEvent_76, Dining Room, {-1})
|-|- 11.2=MEvent_79(AFFRONT<3>, Major Jonathan Winters, MEvent_76, Dining Room, {-1})
|-|- 11.3=MEvent_80(AFFRONT<3>, Ms Hannah Evenworth, MEvent_76, Dining Room, {-1})
|-|- 11.4=MEvent_81(AFFRONT<3>, Miss Miriam Vanderley, MEvent_76, Dining Room, {-1})
|-|- 11.5=MEvent_82(AFFRONT<3>, Mr Elijah Roman, MEvent_76, Dining Room, {-1})

|- 12=MEvent_83(Major Jonathan Winters: (Discussion_Starts)<6>, _, _, Dining Room)[reinc:6]>
      [0, 11.0, 10.0, 11.5, 10.1, 4] <6>
|-|- 12.0=MEvent_84(nothing: (NPCs_React)<3>, _, _, Dining Room, {6})
|-|-|- 12.0.0=MEvent_85(Count Dragos Vladescu: (NPC_Defied)<3>, you, MEvent_77, Dining Room)
|-|-|- 12.0.1=MEvent_86(Mr Elijah Roman: (NPC_Annoyed)<3>, you, MEvent_82, Dining Room)
|-|-|-|- 12.0.1.0=MEvent_87(Mr Elijah Roman: (NPC_Interdicts)<4>, you, MEvent_82, Dining 
                  Room, {365433})
|-|- 12.1=MEvent_88(Major Jonathan Winters: START_STATE<0>, (DST_Discussion), leaving the 
          passenger gondola, Dining Room)
|-|- 12.2=MEvent_89(Major Jonathan Winters: (NPC_Proposes_Plan)<3> [Tell], leaving the 
          passenger gondola, HUNT, Dining Room)
|-|-|- 12.2.0=MEvent_90(Major Jonathan Winters: SUPPORT<3> [Tell], Count Dragos Vladescu, 
              leaving the passenger gondola, Dining Room, {1})
|-|-|-|- 12.2.0.0=MEvent_103(Major Jonathan Winters: AFFRONT<3>, Count Dragos Vladescu, _, 
                  Dining Room, {-1})
|-|-|- 12.2.1=MEvent_91(Major Jonathan Winters: SUPPORT<3> [Tell], Mrs Irene Winters, 
              leaving the passenger gondola, Dining Room, {3})
|-|-|-|- 12.2.1.0=MEvent_102(leaving the passenger gondola: ENDEAR<3>, Mrs Irene Winters, _, 
                  Dining Room, {1})
|-|-|- 12.2.2=MEvent_92(Major Jonathan Winters: SUPPORT<3> [Tell], Ms Hannah Evenworth, 
              leaving the passenger gondola, Dining Room, {5})
|-|-|-|- 12.2.2.0=MEvent_101(Major Jonathan Winters: AFFRONT<3>, Ms Hannah Evenworth, _, 
                  Dining Room, {-4})
|-|-|- 12.2.3=MEvent_93(Major Jonathan Winters: SUPPORT<3> [Tell], Miss Miriam Vanderley, 
              leaving the passenger gondola, Dining Room, {1})
|-|-|-|- 12.2.3.0=MEvent_100(Major Jonathan Winters: ENDEAR<3>, Miss Miriam Vanderley, _, 
                  Dining Room, {1})
|-|-|- 12.2.4=MEvent_94(Major Jonathan Winters: SUPPORT<3> [Tell], Mr Elijah Roman, 
              leaving the passenger gondola, Dining Room, {6})
|-|-|-|- 12.2.4.0=MEvent_99(leaving the passenger gondola: ENDEAR<3>, Mr Elijah Roman, _, 
                  Dining Room, {5})
|-|- 12.3=MEvent_95(nothing: (NPCs_React)<3>, _, _, Dining Room, {6})
|-|-|- 12.3.0=MEvent_96(Miss Miriam Vanderley: (NPC_Replies_To_Opinion)<2> [Tell], 
              Major Jonathan Winters, leaving the passenger gondola, Dining Room, {1})
|-|-|- 12.3.1=MEvent_97(Ms Hannah Evenworth: (NPC_Replies_To_Opinion)<2> [Tell], Major 
              Jonathan Winters, leaving the passenger gondola, Dining Room, {-1})
|-|-|- 12.3.2=MEvent_98(Mrs Irene Winters: (NPC_Replies_To_Opinion)<2> [Tell], Major 
              Jonathan Winters, leaving the passenger gondola, Dining Room, {-2})
System view of two events: a Push deed followed by a Discussion_Starts scene.

First of all, pushing the Count was the 11th event of the story, as shown by the 11 at the start of the first line above. This was cast as a HARASS action, which had a negative affinity impact (-8) on the Count. This wasn't the first time the player pushed the Count in this story though, so this push was also a DEFY (line 11.0) of the Count's earlier request that the player stop being belligerent. This sort of antisocial behavior was also slightly AFFRONT-ing to all the other NPCs that witnessed it (lines 11.1 to 11.5).

The system then responded by resuming an earlier interrupted discussion. This scene as a whole reincorporates a number of earlier events: the start of the story, various previous discussion events, and the defying of the Count. This scene has a number of components. First, NPCs_React plays (12.0). This has the Count express his annoyance at being defied (12.0.0). Elijah Roman also chimes in (12.0.1), warning the player not to bother the Count any more (12.0.1.0).

Then Major Jonathan Winters starts the conversation again (12.1) by proposing a plan (12.2): leaving the passenger gondola in order to hunt the revenant (although at this point in the story, the NPCs do not know yet that it is a revenant they face). This public support for leaving the passenger gondola is equivalent to suggesting the idea to each person individually (12.2.0 to 12.2.4). This has different effects on each listening NPC. For example, Count Vladescu and Ms Evenworth disagree, and so they were AFFRONTed by Major Winters's support of such an idea (12.2.0.0 and 12.2.2.0). They now think slightly less of him. On the other hand, Mrs Winters and Mr Roman think more highly of Major Winters than they do of either leaving or staying in the gondola. So they were swayed by his speech to think more highly of leaving the gondola (12.2.1.0 and 12.2.4.0). Finally, Miss Vanderley already strongly supported leaving the gondola, so hearing Major Winters support it too means she thinks more highly of him (12.2.3.0).

After Major Winters' proposal, NPCs_React plays again to provide some responses (12.3). It would be tedious if every NPC responded every time a proposal was made, so NPC_Reacts has only some those most affected reply. Here, those NPCS with the strongest feelings about the proposal to leave the gondola reply. Note that, in this case, this is not the same as those NPCs with the greatest affinity change. For example, Mrs Winters was convinced just a little to consider leaving, but she still feels very strongly overall that it is a bad idea. Similarly, Elijah Roman's opinion was shifted quite a bit, but, overall, he's still on the fence and so does not reply.

Aside from this complete view of the internals of each event, the events are also connected by reincorporation links. These often express different kinds of causality between events.

This detailed system view is not intrinsically valuable, especially since much of its richness is not narrated effectively. However, a structure like this could serve as a very important foundation for future interactive drama systems. The full details of the past story are available in a machine-readable format that could be used by NPCs explaining their motivation for current actions or when summarizing or narrating offscreen action.

This latter use occurs in Demeter's GoParty_Reports scene. It searches through the details of the event history to discover the details of what the GoParty accomplished: what evidence it encountered, who was attacked and how many times, whether the revenant was slain, whether the PC wandered off or made a nuisance of himself, etc. Because it mines the event history for these details, they can be reported regardless of whether they were all generated by a single GoParty_Offsceen event or by a long, multi-event exploration lead by the player.

Still, there are a problem or two to be worked out of this representation. Because reincorporation links between events are built by Marlinspike based on the list returned from the root scene's canPlay function, all reincorporations for scenes are incorrectly represented as coming from the root event, rather than the specific sub-event that is actually performing the reincorporation. For instance, in the event tree given above, Count Vladescu's NPC_Defied reaction is actually what is reincorporating the PC's DEFY action (11.0). However, this reincorporation is listed as if Discussion_Starts is responsible for it. Like many of Marlinspike's other problems, this stems from Marlinspike's expectation that scenes will be atomic, without such component events.

Notes

  1. The current scene selection process is described in Chapter III. Briefly, each scene that can play next is assigned a score equal to the scene's imperative value plus the weight of both the total material and the unique threaded material that would be reincorporated if the scene was selected to play at this point. The scene with the highest resulting score is selected to play next. The original Marlinspike design did not include an imperative value in this selection process. Instead, it included the import of the current event if the scene were able to reincorporate it.
  2. Regarding the evaluation of Marlinspike, this sort of intra-scene reincorporation is not disabled when the reincorporation feature of Marlinspike is turned off. Although the scenes could have been written to be disabled this way, I feared that the resulting behavior of oblivious NPCs would appear too obviously "broken." For purposes of evaluation, Marlinspike's behavior when reincorporation is switched off was meant to be comparable to other scene-based systems that do not explicitly use reincorporation as a scene-selection mechanism.
  3. In order to evaluate Marlinspike's reincorporation approach on its own merit, this imperative feature can be switched off in the prototype implementation. For the most part, disabling it makes little difference in the resulting stories other than correcting the handful of problem situations described above.