StoryPlaces Technical Overview

A brief introduction to the StoryPlaces reader, authoring tool, and JSON format

Overview

StoryPlaces consists of three core components:

  • StoryPlaces Server: a node.js backend talking to a MongoDB instance (that holds the stories, and also the readings). It has two frontend components that are independent, but which share the information in MongoDB.
  • StoryPlaces Reader: a client-side javascript application (written using the Aurelia framework) which provides anonymous read-only access to the stories and readings in the backend.
  • StoryPlaces Authoring Tool: also a client-side javascript application (again written using the Aurelia framework), which uses Google authentication to provide an authoring interface into the backend.

It may help to think of StoryPlaces as three layers, each one slightly less expressive than than last. The foundation is the conceptual model of sculptural hypertext (as described in some of our academic papers), this is very powerful and potentially allows not only location but a range of contextual information to be included (for example, the weather, or social context). The reader implements a sub-set of this (for example, the only contextual information currently supported is location and time). The JSON schema (see below) gives you an an exhaustive list of what is supported by the reader. The authoring tool supports a sub-set of what the reader can do (although using the Advanced tools almost the full set can be accessed). For example, the reader can deal with page locations and hint locations in different places, while the authoring tool always sets them at the same place. Similarly the reader allows for conditional functions, while the authoring tool does not. This was a deliberate decision in order to make the authoring tool as accessible as possible.

StoryPlaces JSON Format (version 2.0)

One of the guidelines for the StoryPlaces design was that stories should be downloadable as a single file, so that authors could keep a record of them (although if they reference media files like audio or video these are separate). The system uses JSON as the format for both import and export.

There is a JSON schema definition for the file that you can download from the GitHub repository, but what follows is an informal description of the structure.

Please note: you can write these story formats by hand and import them directly, but although the reader does do validation before inserting the story into the backend it will only give you limited feedback if there is an error. Also, the authoring tool can export to this format, but it will not import from this format (as it requires additional information not included in the export). So where possible it is best to use the authoring tool rather than code by hand.

The JSON story file contains the following parts:

  • General properties of the story (e.g. schema version, title, author, description, classification, tags).
  • An array of pages representing the content and linking to conditions, locations, and functions.
  • An array of functions, named operations on the story state that can be evoked when a page is read.
  • An array of conditions, named constraints that define whether a page is visible according to the story state, referenced by the pages.
  • An array of Media used by the story (e.g. images or audio files) that can be referenced by the Pages.
  • An array of Locations used by the story (that can be referenced by conditions)

Pages

In general a page represents an item of content. It has conditions that determine whether it can be seen given the story state, and functions that it invokes to change the story state when read. Conditions can refer to the state of named variables, or the location of the reader (which can be compared against the list of locations). Functions set or alter named variables. Content is embedded text, but can include references to Media items.

The following is an example page from the Isle of Brine story:


{
        "content": "<img style='max-width: 100%; display: block; margin: auto;'
         data-media-id='9f602e952aa55150ec60706d9e0aef76'/>
         
         These are the graves of the servicemen who were stationed here during the war
         and never got to go home. Those of us who traverse the great oceans know that the
         sky and the water are one. Sailors or airmen, I have shared the same cauldron as
         these men. I fumble with my documents; somewhere there is a black and white
         photograph of the funeral held here at Soroby in August '44 for the airmen who
         died in a mid-air smash.
         
         Here it is, with a note.
         
         One of the dead was Flt. Lt. Leonard Revilliod whose grandfather, Tomas Masaryk,
         was the first President of Czechoslovakia. I look and find him. His stone sits in
         the back row, nestled in the crook of a dry stone wall, one precious son amongst
         many precious sons.",
        
        "hint": {
            "direction": "An ordered row of disordered deaths.",
            "locations": ["249d8fb83776cdb1aa5b7559"]
        },
        
        "functions": ["seen-Airmen", "increment_phase2_count"],
        "conditions": ["notSeen-Airmen","tireephase2", "bali_gravewar"],
        
        "name": "Airmen",
        "pageTransition": "next",
        "id": "Airmen"
    }

The hint is used to tell visual components (such as the map and list) how to display this page when the reader is trying to find them. For example, the list component displays the direction string underneath the name; many stories use this to aid navigation (e.g. showing a message such as "at the back of the graveyard") but here it is used more poetically. Similarly, the map component uses the location to decide where to place the pin (or pins) on the map. This means that the pin(s) shown and the actual place that the story is situated can be different.

The pageTransition is simply an id that is used by the reader to pick which UI control appears at the bottom of a page of content. At present the only supported elements are a next button, which returns the reader to the main navigation screen, or an end button, which ends the story.

Most of the power of the StoryPlaces engine comes from the interplay of Functions and Conditions, which are described further below:

Functions

Functions are named operations that set or alter the value of state variables, which can then be checked by conditions. In version 2 StoryPlaces supports the following functions:

Set - this sets a variable to a given value. For example:

{
      "type": "set",
      "conditions": [],
      "variable": "seen-TheFarmhouseCafe",
      "value": "true",
      "id": "seen-TheFarmhouseCafe"
}

SetTimeStamp - this sets a variable to the current time. For example:

{
      "type": "settimestamp",
      "conditions": [],
      "variable": "began-story",
      "id": "set-time-started"
}

Increment - this increments a given variable by the value passes, and is useful for counting. For example:

{
        "conditions": [],
        "type": "increment",
        "variable": "histphase1_count",
        "value": "1",
        "id": "increment_histphase1_count"
}

In all three cases the functions can optionally contain a set of conditions that must all evaluate to be true before the function can be executed. These are simple referenced by name. For example:

{
        "conditions": ["met-all-characters"],
        "type": "increment",
        "variable": "current-act",
        "value": "1",
        "id": "progress-if-ready"
}

Conditions

Conditions are typically used to restrict when a page is visible to the reader (although as shown above they can also be used to create conditional functions). In total there are six different types of condition as follows:

Comparison - this compares two different variables. For example:

{
        "aType": "Variable",
        "a": "pipes",
        "bType": "String",
        "b": "true",
        "operand": "==",
        "type": "comparison",
        "id": "has_heard_pipes"
}

StoryPlaces supports the following conditional operands:

  • == equal to
  • != not equal to
  • <= less than or equal to
  • >= greater than or equal to
  • < less than
  • > greater than

Check - this returns true if the given variable has been defined (set by a function in the current reading)

{
        "variable": "last-spoke-to",
        "type": "check",
        "id": "has-spoken-to-character"
}

Location - this compares the readers current location to one of the pre-defined locations in the story. If book is set to true then it returns true if they are in that location, if it is set to false it returns true when they are not in that location. Locations could potentially be defined in several ways. In version 2 they are always a point and a radius, but this is managed by the location object, not the condition.

{
        "bool": "true",
        "type": "location",
        "location": "north-path-location",
        "id": "is-at-the-north-path"
}

Logical - this allows other conditions to be chained together using the normal set of logical operators.

{
        "conditions": ["in-act-one", "in-act-two"],
        "operand": "OR",
        "type": "logical",
        "id": "in-acts-one-or-two"
}

StoryPlaces supports the following logical operands:

  • OR true if any of the set of conditions are true
  • AND true if all of the set of conditions are true

TimePassed - this returns true if a certain number of minutes have passed since the time stored in a given variable. For example:

{
        "type": "timepassed",
        "minutes": 30,
        "variable": "began-story",
        "id": "30min-after-start"
}

TimeRange - this returns true if the current time lies within a given range (given in the 24 hour format of hh:mm). For example:

{
        "type": "timerange",
        "first": "12:00",
        "last": "18:00",
        "id": "is-the-afternoon"
}