Curveship

Introduction

Curveship is a system for authoring “variable narratives,” and for automatically generating different narrative texts.

The system is a computational model of narrative. It represents an underlying story World with Actors who are in Places. These, and Things, participate in Events. A crucial aspect of Cureveship is that different narratives can be generated based on the same underlying content. This is done using different expression files which can each have different spin, Names, and VerbPhs (verb phrases).


To author a variable narrative in Curveship, you define a story world and one or more expressions of it. This is done using JavaScript files, but you don’t have to know how to program to create these files.

Curveship is a framework for defining the underlying story World (what exists in it: Existents, and what happens in it: Events) as well as how that story is to be expressed in the narrative discourse. Essentially, you define everything about this in a form that looks like JavaScript. Strictly speaking, what you write are snippets of JavaScript! However, you never, for instance, need write a for loop or learn about the functions having arguments and return values. You just follow the scheme provided.

Also, it’s perfectly fine to develop a new Curveship story and narrator files is by modifying existing files. Indeed, this can be a very good idea.

In this introduction, however, we’ll explain how to define a story world called “The Simulated Bank Robbery” from scratch. Then, we’ll explain how to define spin, initially to specify a narrative voice detached from the events (and a text without an “Í”) and then to specify more of an eyewitness who is addressing one of the characters (so the narrative has not only an “Í,” but also a “you.”) We’ll conclude by defining the specific language that is to be used in expression, Names and VerbPhs (verb phrases).

Writing the Story File

One principle in Curveship is that easy things should be easy. For instance, it should be easy to define a story that is like a folk tale, with straightforward elements and little underlying complexity in terms of what fundamentally happens. This document goes line-by-line through one reasonably simple example of content (an underlying story) robbery/story.js along with two associated narrator files, robbery/detached.js and robbery/witness.js.

A Curveship file defining the content (always called story.js) just needs:

  1. A single line defining the story’s title.
  2. Lines defining all the Places.
  3. Lines defining all the Actors.
  4. Lines defining all the Things.
  5. Lines defining a sequence of Events.

1. Title

Put the title of your story right up top, after that comment:

const title = "The Simulated Bank Robbery";

2. All the Places

In this underlying story there are only four places:

place.vestibule = new Place();
place.lobby = new Place();
place.guardPost = new Place();
place.street = new Place();

These four places have four tags, “vestibule”, “lobby”, “guardPost”, and “street”. The tags are not what these places end up being called when the story is narrated. Actually, nothing in the story file indicates what these places, or anything else, will be called. Tags are just the way for you to refer to these places when you’re composing your variable narrative. Tags need to be unique not only across all the Places, but across all Existents. For instance, not only can there only be one Place with the tag “street” — once that tag has been used for a Place, it also can’t be used for an Actor or a Thing.

3. All the Actors

After this, list your Actors. You use the same syntax, but this time arguments are included to indicate where the Actor is and (optionally) what gender the actor is:

actor.teller = new Actor(place.vestibule, "female");
actor.robber = new Actor(place.street, "male");
actor.guard = new Actor(place.guardPost, "male");

4. All the Things

Of the three sorts of Existents, Things come last. These are also each located somewhere. Their location can be a Place, an Actor (if an Actor is carrying them), or even another Thing (if that other Thing is a container, for instance).

thing.slip = new Thing(place.vestibule);
thing.fakeMoney = new Thing(place.vestibule);
thing.bag = new Thing(place.vestibule);
thing.mask = new Thing(actor.robber);
thing.fakeGun = new Thing(actor.robber);
thing.pistol = new Thing(actor.guard);

At the end of this sequence, we can (optionally) provide some additional information, for instance, information about Actors owning Things. This can end up having implications for the generation of possessive noun phrases, eventually:

thing.fakeGun.setOwner(actor.robber);
thing.pistol.setOwner(actor.guard);

5. The Sequence of Events

What remains is to provide a sequence of Events, describing what happens. Events are given tags, just like Existents. They always have an agent (the actor who is performing the action, named first). For “intransitive” actions, that’s usually all there is. But they can also have an object (what is acted upon). If they do, that's the second argument. Here’s the beginning of the Event sequence, where events 1, 3, 4, and 8 have direct objects:

ev.read = new Event(actor.teller, thing.slip);
ev.snooze = new Event(actor.guard);
ev.reread = new Event(actor.teller, thing.slip);
ev.coverFace = new Event(actor.robber, thing.mask);
ev.type = new Event(actor.teller);
ev.play = new Event(actor.teller);
ev.beginRobbing = new Event(actor.robber);

Events can alter Existents. This means that, at the story or content level, Places, Actors, and Things can be different before and after certain events. Not every event will make such an alteration, because Curveship doesn’t model every detail of the world. But, for instance, Actors moving from one place to another, or handing Things to one another, or transferring ownership of Things, can be represented.

The Event with the tag “beginRobbing” corresponds to the (fake) bank robber entering the bank lobby from the street. In Curveship 0.4 there are no actual implications, but in future versions the system should be automatically able to focalize different Actors, which will make use of the locations of Existents. For now, this is how to model the robber moving from the street to the lobby:

ev.beginRobbing.alters(actor.robber, "location", place.street, place.lobby);

The next event doesn’t have any associated alteration:

ev.wave = new Event(actor.teller, actor.robber);

If an Event also has an indirect object (something indirectly affected by the action), a temporal relationship needs to be specified as the third argument. Then the indirect object is the fourth argument, as seen in the next event:

ev.threaten = new Event(actor.robber, actor.teller, temporal.using, thing.fakeGun);
ev.laugh = new Event(actor.teller);
ev.wake = new Event(actor.guard);
ev.seeThreat = new Event(actor.guard, actor.robber);
ev.leavePost = new Event(actor.guard, place.guardPost);
ev.grabFake = new Event(actor.teller, thing.fakeMoney, temporal.into, thing.bag);

Note that while Existents can be moved between Places, they can also, for instance, be moved into a Thing that is a container:

ev.grabFake.alters(thing.fakeMoney, "location", place.vestibule, thing.bag);
ev.turn = new Event(actor.robber, actor.guard);
ev.shoot1 = new Event(actor.guard, actor.robber);
ev.shoot2 = new Event(actor.guard, actor.robber);
ev.fall = new Event(actor.robber);
ev.die = new Event(actor.robber);
ev.dropGun = new Event(actor.guard, thing.pistol);

Events can alter other properties of Existents, not only their location. When the guard drops the gun, for instance, according to this underlying story, he “disowns” it. It no longer belongs to him:

ev.dropGun.alters(thing.pistol, "owner", actor.guard, null);

This actually can have an implication in how the narrative is generated. The pistol is described using a possessive adjective before the guard drops and disowns it, but using just an article afterwards.

Here’s the rest of the Event sequence:

ev.regret = new Event(actor.guard, ev.shoot1);
ev.cry = new Event(actor.teller);
ev.stare = new Event(actor.guard, thing.pistol);

Writing a “Witness” Narrator File

Once the content is defined in story.js, you will need at least one expression file (ending in “.js”). The file should be given some sort of descriptive name, for instance precise.js when it defines a precise narrator and vague.js when it defines one who speaks generally and vaguely. This file specifies how the narrative discourse or expression is to be generated. It can also define higher-level aspects of narration. A story file may have many narrator files. In fact, it’s Curveship’s whole point to show how the same underlying can be told in different ways.

Take a look at the HTML files in the example/robbery folder and note that there are two different narrator files ending with .js there. We’ll now consider witness.js as if we were writing this file from scratch.

Again, include a comment with the critical information at the top, or modify what’s there if you are changing an existing file. Also, give your expression file a name or short description so that we can see who this narrative is told by:

const toldBy = "the bank teller";

Spin. The next section of the narrator file sets spin parameters. These parameters allow Curveship to put a particular “spin” on the overall narrative, as when a journalist puts a particular spin on a news story. The particular parameters are based on narratology. In this narrator file, there are two spin parameters. The first one is pretty straightforward. It sets the “I” of the story to be the bank teller:

spin.i = actor.teller;

The second one defines a main narrative sequence that includes only a subsequence of all the events. The bank teller is only going to tell us about what she saw. She isn’t going to narrate what happened out on the street or in the guard post, which is behind a one-way mirror. So we define a sequence that allows for the sort of ellipsis (leaving things out) that we are interested in:

spin.main = "0;2;4;5-9;12-22";

This looks a little complex, but it’s the same sort of thing you would specify if you only want to print out only a selection of pages from a longer document.

Names — Naming Existents. Next, the Names corresponding to the existents are declared. These names must match the tags for Existents that are declared in the story file—that’s how Curveship knows to connect an Existent with Names. The convention is to order the Names the same way that they appear in the story file, so the Names for Places will be first, then for Actors, and lastly, for Things.

As you can see in witness.js and other examples, “Names” is written in the plural. That’s because, in general, there can be many names used by a narrator for a single Existent.

The simplest way to name an Existent is to provide Names with one string, including an article and a noun (or noun phrase). The first time the Existent appears in the narration, that exact name will be used. After that, if the name begins with an indefinite article such as “a” or “an,” the system will use the definite article, “the.”

names.lobby = new Names("the lobby");

If you provide two strings as arguments to Names, the first one will be used on first reference and the second one (exactly as it is specified) will be used on any subsequent references.

names.street = new Names("a sidewalk outside a bank", "the sidewalk");
names.guardPost = new Names("the guard post");

You may notice that some Names are “missing” — Names are not defined for each Existent. That’s all right in many cases, for a few reasons. The bank teller never needs to refer to the vestibule that she’s in, so there is no need to define a name for that Place. Also, because the bank teller is the “I” of the story, she never gives her own name, and there is no need to have Names defined for the teller, either.

When it comes to naming the (fake) robber, this narrator file uses a special means of naming. The bank teller is in on the fake robbery and knows the supposed robber personally, remember. So of course she knows his name. By defining ProperNames a first and last name for this Actor can be specified. As shown in The Story of an Hour, a courtesey title can also be specified using ProperNames. But here, a first and last name is enough:

names.robber = new ProperNames("Jimmy", "Smith", pronoun.masculine, "my friend");

Pronouns can be specified whether you are using Names or ProperNames, and there are some interesting possibilities provided by being able to specify these at the narrative level. To get started, however, you don’t need to go into that in detail. The previous line also defines a common name for Jimmy, “my friend,” which will never get used by Curveship 0.4 but has been put in place for future versions of the system.

Actors aren’t required to have ProperNames, of course. This narrator doesn’t happen to know the name of today’s guard:

names.guard = new Names("our guard", "the guard");

There shouldn’t be too many surprises about the Names assocated with Things. You will see that the bank teller, given her profession, does have a very specific name for the deposit slip:

names.slip = new Names("a completed Form D-22", "the deposit slip");
names.fakeMoney = new Names("some fake money");
names.bag = new Names("a black bag", "the bag");
names.mask = new Names("a Dora the Explorer mask", "the mask");
names.fakeGun = new Names("a gun-shaped object", "the fake gun");
names.pistol = new Names("a pistol");

Note that there are experimental features of Curveship to allow the naming of Existents according to Categories, based on what Existents are parts of, and based on the properties Existents have. You are welcome to try them and to let us know what isn’t working about them. They are not supported in this 0.4 release, however.

Verb Phrases: Representing Events. Just a narrator has idiosyncratic ways of naming each Existent, that narrator also has a particular way of representing each Event. This is done using a VerbPh for each Event. The tag assigned each VerbPh must correspond to the Event tags from the story file.

vp.read = new VerbPh("glance at");
vp.reread = new VerbPh("look over");
vp.type = new VerbPh("do some data entry");
vp.play = new VerbPh("play Solitaire");
vp.beginRobbing = new VerbPh("pretend to rob");
vp.wave = new VerbPh("wave to");
vp.threaten = new VerbPh("pose for");
vp.laugh = new VerbPh("laugh");
vp.leavePost = new VerbPh("pop out of");
vp.grabFake = new VerbPh("place");
vp.turn = new VerbPh("turn to");
vp.shoot1 = new VerbPh("shoot");
vp.shoot2 = new VerbPh("execute");
vp.fall = new VerbPh("fall");
vp.die = new VerbPh("die");
vp.dropGun = new VerbPh("drop");
vp.regret = new VerbPh("recall");
vp.cry = new VerbPh("weep");
vp.stare = new VerbPh("stare at");

A VerbPh can contain prepositions, and so could be defined as “glance at” or “wave to”. It can also include an infinitive after the main verb, as in “pretend to rob.” There are a few other longer phrases that never need tobe inflected differently and can be baked into a VerbPh; “do some data entry” is one, which is the teller’s way of saying “type”. However, a VerbPh is not a full verb phrase in the linguistic sense. It doesn’t contain any direct or indirect objects that correspond to Existents. Nor does it hold adverbs indicating the manner in which an action is done.

Finally, a narrator file always ends with a standard incantation, invoking the run function so that Curveship can realize the narration. The narrator file must end with the following line:

function run() { narrate(title, toldBy, world, spin, names, vp);
}

With a story file and one initial narrator file defined, you are ready to view the output. In the next section, you'll tell Curveship how to link up the story and narrator files.

Connecting Story and Narrator with a Web Page

Curveship realizes a narrative, in HTML, using the story file and a narrator file.

To link a story file to a particular narrator file, create (or modify) an HTML file that matches the descriptive filename of the narrator file created above. In the present case, the narrator file is named “witness.js” and so the corresponding HTML file is “witness.html”.

The crucial parts of your HTML file are the last two script elements. They connect your story file with a particular narrator file:

<script src="story.js">
<script src="witness.js">

The rest of the HTML file is formulaic, and you don’t need to change the other references to files unless you move where those files are. Be sure, however, to change the title (in the title element) to reflect your particular story.

To view the generated output, open the HTML file in a web browser.

A Different, “Detached” Narrator

Now that you’ve seen how one narrator is defined, take a look at examples/robbery/detached.js to see how a different sort of narration can be accomplished. This file doesn’t have spin.i defined at all, because there is no “I” of the narrative. It’s told by someone who is not an Actor. There’s also no definition of spin.main because this narrator represents all of the Events. But there are spin parameters set. Check the Technical Reference entry on spin to learn more about what these do and to learn other ways spin can be set.

You can also see that this narrator has some different Names assocaited with Existents and different VerbPh specifications for Events. This narrator doesn’t have Names specified for the bank teller, but Curveship provides default names in this case. For an Actor who is female and an adult, “woman” is generated in the output text when Names are not specified.

This narrator has more VerbPh definitions, because this one narrates all of the Events. Narrators have a “default” or “generic” way of representing events, too, using the verb “act.” That’s almost never a good way to represent an event! So you probably don’t want to leave any Event without a corresponding VerbPh.

Further Explorations

This line-by-line explanation is meant to be of help for those new to Curveship, but another great way to understand the system is to study the existing examples and modify them. By seeing what your modifications do, you will be able to figure out more ways to get interesting narrative effects out of the system. Check out the other three examples in examples/ — and noodle around with them, changing story and narrator files, to see how they work.

Curveship