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.