Case ยท language & engine

StoryScript

A language I designed for point-and-click adventures, and the headless engine that runs it. You write the story like a screenplay — no JSON, no code — and it plays. The interesting part is what that unlocks: you can test a branching narrative with AI agents in a terminal before a single pixel is drawn.

My own DSL Console-first Proof on request

Problem

Two problems, one root. Writers should not have to learn JavaScript to write a story, and you cannot properly test a branching narrative when it is tangled inside game code and a renderer. Every playtest means launching the whole game and clicking through by hand, so most branches never get exercised and dead ends ship. The story needs to be its own thing: readable by a writer, executable on its own, and checkable by a machine.

What I Built

StoryScript is a small language and a direct interpreter for it. A .story file is plain text — scenes, hotspots, dialogue, choices, inventory, and flags — and the runtime reads it and executes line by line with a state machine. There is no JSON, no compiled AST, no build step between writing and playing. It runs in a terminal REPL, and because it is headless it is also the test harness: the same engine that plays the game can validate it in CI.

The StoryScript interpreter running in a terminal: it prints the Arkham Bus Depot scene, lists hotspots with their verbs, starts a dialogue with choices, and flags an unknown goto target during validation.
The interpreter playing a real .story file in the terminal — scene text, hotspots with their verbs, a dialogue with choices. The last line shows the validator catching a dangling goto (Unknown target "grocery_store") with no game running.

The Language

A scene declares a place, what is in it, and what the player can do with each thing. Verbs (LOOK, TALK, USE) hang off hotspots; a goto (->) jumps to a dialogue or another scene.

SCENE bus_station
  location: "Arkham Bus Depot"

  DESCRIPTION
    A small-town bus depot. Diesel and stale coffee.
  END

  HOTSPOT ticket_window
    name: "Ticket Window"
    LOOK
      "Scratched plexiglass. Someone carved 'DONT GO'."
    END
    TALK
      -> buy_ticket
    END
  END
END

Dialogue is a branching tree. Choices can set flags and hand the player items, and the state machine remembers all of it, so a later scene can react to what happened here.

DIALOGUE buy_ticket
  clerk: "Innsmouth? You sure?"
  CHOICE
    > "One ticket."
      SET going_to_innsmouth
      GIVE bus_ticket
      -> END
    > "Never mind."
      -> END
  END
END

TRIGGER board_bus
  REQUIRE HAS(bus_ticket)
  CUTSCENE ...

CI for storytelling

This is the payoff of keeping the story headless. The whole game is a state machine you can drive without graphics, so an agent can play the entire narrative in seconds — walk every scene, take every dialogue branch, pick up every item — and do it a hundred times over. The interpreter validates the story graph as it loads and reports broken links (Unknown target "grocery_store") before anyone plays. Roughly 6,500 lines of StoryScript author the full four-act game, and all of it can be exercised in CI without rendering a pixel.

Bring your own UI

The engine emits what happened — scene entered, line spoken, choice offered, item taken — and does not care what draws it. A terminal renders it as text; a React or Phaser layer renders it as a graphic adventure. My graphic-adventure system, Anima, is one such UI sitting on top of StoryScript. The language and the pixels stay independent.

Technical Decisions

  • Make the story its own plain-text language, not JSON or engine code — a writer can read and edit it, and an LLM can draft it.
  • Interpret directly with a state machine, no intermediate AST or build step — the source is the runtime.
  • Keep the engine headless so it doubles as the test harness — play and validate through the same code path.
  • Separate the engine from the renderer with an event stream, so terminal, React, and Phaser are all just subscribers.
  • Validate the story graph on load and surface dangling gotos and unreachable scenes as warnings.

What This Proves

StoryScript is language-design judgment applied to a real problem: I built a small DSL, its parser, and its runtime, then wrote a whole game in it. It shows the instinct to make the right thing the format — readable for writers, generable for models, executable and testable for machines — instead of bolting a story onto a game engine.