Skip to content

Core Concepts

Spectator models stories as structured data — not just text. Every story is composed of typed objects that you define and the engine generates.

World

A World defines the setting, genre, tone, and rules of the story universe.

typescript
import { World } from '@spectator-ai/core'

const world = World.create({
  genre: 'cyberpunk',
  setting: 'Neo-Tokyo, 2087. Megacorporations rule from gleaming towers.',
  tone: 'gritty, neon-drenched, melancholic',
  rules: [
    'Cybernetic augmentation is common but addictive',
    'AI is outlawed after the Collapse of 2071',
  ],
  constraints: ['Keep scenes under 500 words'],
})

All fields are optional. If you omit World entirely, the engine will generate without world context.

Worlds are immutable — extend() returns a new instance:

typescript
const darkWorld = world.extend({ tone: 'bleak and hopeless' })

Character

Characters are defined with a name (required) and optional traits, backstory, goals, personality, and relationships.

typescript
import { Character } from '@spectator-ai/core'

const zero = Character.create({
  name: 'Zero',
  traits: ['resourceful', 'paranoid', 'loyal'],
  backstory: 'A data runner who lost their memory after a botched Mesh dive',
  goals: ['Recover lost memories', 'Expose the truth about the Collapse'],
  personality: 'Guarded but fiercely loyal to the few they trust',
})

Builder Methods

Characters are immutable. Builder methods return new instances:

typescript
// Add a relationship
const withRelation = zero.withRelationship({
  target: 'Glass',
  type: 'handler',
  description: 'Provides jobs in exchange for loyalty',
})

// Add traits
const withMoreTraits = zero.withTraits('determined', 'resourceful')

// Override any fields
const extended = zero.extend({ backstory: 'A reformed hacker...' })

Relationships

Relationships link characters together:

typescript
const glass = Character.create({
  name: 'Glass',
  traits: ['calculating', 'connected'],
}).withRelationship({
  target: 'Zero',
  type: 'handler',
  description: 'Provides jobs and protection in exchange for loyalty',
})

The type field is freeform — use whatever describes the relationship: 'nemesis', 'mentor', 'ally', 'handler', 'rival', etc.

Plot

A Plot defines the narrative structure through a sequence of beats — named story checkpoints that guide generation.

typescript
import { Plot } from '@spectator-ai/core'

const plot = Plot.create({
  name: 'The Memory Job',
  beats: [
    { name: 'The Job', type: 'inciting-incident', description: 'An offer that might unlock the past' },
    { name: 'Into the Mesh', type: 'rising-action' },
    { name: 'The Truth', type: 'climax' },
    { name: 'The Choice', type: 'resolution' },
  ],
})

Each beat generates one scene. The type tells the engine what emotional role this beat plays in the narrative arc.

Beat Types

TypeRole
setupEstablish characters and world
inciting-incidentThe event that disrupts the status quo
rising-actionEscalating conflict and complications
midpointA major turning point
crisisThe darkest moment
climaxThe final confrontation
falling-actionThe aftermath
resolutionThe new normal

Plot Templates

Use Plot.template() to load a registered template (requires @spectator-ai/presets or custom registration):

typescript
import '@spectator-ai/presets'

const plot = Plot.template('hero-journey')

Register your own templates:

typescript
Plot.registerTemplate('my-template', {
  beats: [
    { name: 'Opening', type: 'setup' },
    { name: 'Conflict', type: 'climax' },
    { name: 'Ending', type: 'resolution' },
  ],
})

Builder Methods

typescript
// Add a beat at the end
const withBeat = plot.withBeat({ name: 'Epilogue', type: 'resolution' })

// Insert a beat at a specific position
const withInserted = plot.withBeat({ name: 'Flashback', type: 'setup' }, 2)

// Remove a beat by index
const withoutBeat = plot.withoutBeat(0)

Scene

A Scene is an individual narrative unit generated by the engine. Each beat in the plot produces one scene.

typescript
scene.id              // Unique identifier
scene.text            // The narrative text
scene.beat            // The plot beat that generated this scene
scene.location        // Where the scene takes place
scene.participants    // Character names involved
scene.summary         // Auto-generated summary
scene.characterStates // Character emotional/goal states after the scene

Scenes are typically not created manually — they are returned by the engine.

Story

A Story is the complete output: a collection of scenes with metadata.

typescript
story.scenes          // Scene[] — all scenes in order
story.text            // All scene text joined with separators
story.title           // Optional title
story.wordCount       // Total word count
story.sceneCount      // Number of scenes
story.characterStates // Character states from the final scene

story.toMarkdown()    // Formatted markdown output
story.toJSON()        // Serializable JSON

Stories can be serialized and restored:

typescript
const json = story.toJSON()
const restored = Story.fromJSON(json)

Architecture

Spectator uses a multi-agent pipeline:

ComponentRole
EngineManages the generation pipeline, prompting, and scene analysis
DirectorMaps world events to the emotional trajectory (roadmap)
CanvasVisualizes timelines and threads (roadmap)

Decoupled World vs. Camera (Fabula & Syuzhet)

Spectator separates the raw chronological events (the World State) from the narrative delivery (the Camera). This means you can generate a world history once, then render it through different lenses — flashbacks, interleaving timelines, or different character POVs.

Emotional Trajectory Mapping

Each beat type carries emotional weight. The engine uses beat types to shape pacing — setup is calm, crisis is tense, resolution brings closure.

Multi-Stream Timelines

World events can exist as independent concurrent streams that diverge (parallel character arcs) and converge (shared event nodes).

Explicit vs. Implicit Threading

Tag events as Explicit (what the viewer sees) or Implicit (what happens in the shadows). The engine maintains state consistency across both threads.

Released under the MIT License.