Tracery is a primarily JavaScript text-expansion library that works from grammar expressions written in JSON to create generative, flattened options.
ink is a scripting language for creating interactive narratives. It can be written using the editor Inky. The inkjs NPM module is a JavaScript port of the ink engine.
Reviewing Tracery
The Tracery library works through creating a generative expression through using different tokens to signal that a random option should be taken from an array representing that token in JSON.
{
"name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"],
"animal": ["unicorn","raven","sparrow","scorpion","coyote","eagle","owl","lizard","zebra","duck","kitten"],
"mood": ["vexed","indignant","impassioned","wistful","astute","courteous"],
"origin": ["#name# traveled with her pet #animal#. #name# was never #mood#, for the #animal# was always too #mood#."]
}
In the above JSON, for example, the expression starts with the symbol origin. Building from there, the first symbol, name, is translated into one of its options. Next, the symbol animal, is translated into one of its options. Finally, the symbol mood is also translated.
As the result of each run is random, the possible combinations quickly expand.
Azra traveled with her scorpion. Azra was never indignant, for the scorpion was always too indignant.
Reviewing Ink
Tracery doesn’t directly exist in the Ink programming language. However, a similar example from the above Tracery JSON can be created through its own functionality.
LIST names = (Arjun), (Yuuma), (Darcy), (Mia), (Chiaki), (Izzi), (Azra), (Lina)
LIST animals = (unicorn), (raven), (sparrow), (scorpion), (coyote), (eagle), (owl), (lizard), (zebra), (duck), (kitten)
LIST moods = (vexed),(indignant),(impassioned),(wistful),(astute),(courteous)
VAR name = ""
~ name = getName()
VAR animal = ""
~ animal = getAnimal()
VAR mood = ""
~ mood = getMood()
{name} traveled with her {animal}. {name} was never {mood}, for the {animal} was always too {mood}.
=== function getName() ===
~ return LIST_RANDOM(names)
=== function getAnimal() ===
~ return LIST_RANDOM(animals)
=== function getMood() ===
~ return LIST_RANDOM(moods)
The LIST type (with default, enabled values) can act as arrays for our values. Using the LIST_RANDOM() function, a random entry can be retrieved from a list. Generating name, animal, and mood values can be done through encapsulating those calls within functions named after their usage.
During run-time, the variables name, animal, and name are overwritten from their default value, “”, to a new one. The random results are then displayed to the user.

Thinking in JavaScript
While examples could be written in Tracery’s JSON and then “ported” over to Ink through re-writing them, there is a simpler solution: using JavaScript as a bridge between them. Both Tracery and inkjs run in JavaScript, and Tracery results could be fed into an Ink story while it is running.
Tracery | Ink | |
Holds grammar | Starts Story | |
Flattens grammar | -> | Holds result |
Produces Story Content From Result |
In working with two different libraries, some planning is needed. Tracery has its own functions. So too does the inkjs API. In working with both, an interface layer is needed to bridge the two, and to isolate their functionality more cleanly. To start, the variablesState proxy in inkjs could be used to “inject” values into the Ink story after Tracery computes them.
InkStory.ink
VAR name = ""
VAR mood = ""
VAR animal = ""
{name} traveled with her {animal}. {name} was never {mood}, for the {animal} was always too {mood}.
tracery.json
{
"name": ["Arjun","Yuuma","Darcy","Mia","Chiaki","Izzi","Azra","Lina"],
"animal": ["unicorn","raven","sparrow","scorpion","coyote","eagle","owl","lizard","zebra","duck","kitten"],
"mood": ["vexed","indignant","impassioned","wistful","astute","courteous"]
}
index.js
var Story = require('inkjs').Story;
var Tracery = require('tracery-grammar');
var fs = require('fs');
var inkJSON = JSON.parse( fs.readFileSync('./InkStory.json', 'UTF-8').replace(/^\uFEFF/, '') );
var inkStory = new Story(inkJSON);
var traceryJSON = JSON.parse(fs.readFileSync('./tracery.json') );
var grammar = Tracery.createGrammar(traceryJSON);
var name = grammar.flatten("#name#");
var mood = grammar.flatten("#mood#");
var animal = grammar.flatten("#animal#");
inkStory.variablesState["name"] = name;
inkStory.variablesState["mood"] = mood;
inkStory.variablesState["animal"] = animal;
console.log(inkStory.Continue() );
Play with the example on Repl.it!
Building an API
The use of variablesState proxy in inkjs is a viable way to go about changing the values of variables. However, it is not as clean as it could be. For that, more functions could be added on the Ink side. These could then encapsulate their own work and allow for the Ink side to handle data however it wants.
InkStory.ink
VAR name = ""
VAR mood = ""
VAR animal = ""
{name} traveled with her {animal}. {name} was never {mood}, for the {animal} was always too {mood}.
=== function setName(a) ===
~ name = a
=== function setMood(a) ===
~ mood = a
=== function setAnimal(a) ===
~ animal = a
index.js
var Story = require('inkjs').Story;
var Tracery = require('tracery-grammar');
var fs = require('fs');
var inkJSON = JSON.parse( fs.readFileSync('./TraceryInk.json', 'UTF-8').replace(/^\uFEFF/, '') );
var inkStory = new Story(inkJSON);
var traceryJSON = JSON.parse(fs.readFileSync('./tracery.json') );
var grammar = Tracery.createGrammar(traceryJSON);
var name = grammar.flatten("#name#");
var mood = grammar.flatten("#mood#");
var animal = grammar.flatten("#animal#");
inkStory.EvaluateFunction("setName", [name]);
inkStory.EvaluateFunction("setMood", [mood]);
inkStory.EvaluateFunction("setAnimal", [animal]);
console.log(inkStory.Continue() );