Introduction to Loreline

Loreline is an open-source scripting language for writing interactive fiction. It handles branching dialogue, player choices, characters, and story state in a syntax designed to be readable by writers while still offering real programming tools (variables, conditionals, functions) when the story needs them.

Loreline works everywhere: game engines, web apps, or standalone projects. It adapts to your tools, the stories you write stay portable.

A first look

Here is a minimal Loreline script:

The warm aroma of coffee fills the café.

barista: Hi there! How are you doing today?

choice
  Having a great day
    barista: Wonderful! Coffee will make it even better.

  Need caffeine...
    barista: Say no more! Let me help with that.

  Your name is Alex, right?
    barista.name = "Alex"
    barista: Oh, I didn't expect you'd remember it!

Text on its own is narration. barista: ... is dialogue spoken by a character. choice presents options, and indentation determines what happens after each option is picked. That's the core pattern. The Writer's Guide covers everything in detail.

Clean yet explicit by design

Loreline keeps additional punctuation to a minimum. Narrative text and dialogue can be written as-is, without any special delimiter. Jumping to another section is just ->. Assigning a variable is just =. Conditions don't need a closing keyword. A choice option can carry an inline if that hides it when the condition isn't met.

At the same time, the main structural elements (beat, choice, character, state, if, else) are plain English keywords rather than symbols. They're easy to understand at a glance, without needing to learn what each special character means. This is a deliberate trade-off: readable words over cryptic punctuation.

Other tools lean more on symbols or delimiters: Yarn Spinner uses <<if>>...<<endif>> and <<set>>; Ink has its own set of markers for choices, knots, and diverts. Loreline tries to stay close to plain text, and the editor is smart enough to apply correct syntax highlighting from those minimal cues alone:

The café is quiet this morning.

choice
  Order a coffee
    coffeesOrdered += 1
    if coffeesOrdered > 3
      barista: <concerned> That's quite a lot of coffee today...
    else
      barista: One more coming right up!

  Order a decaf if coffeesOrdered > 2
    barista: Good call. I'll make it a nice one.

For writers, this means the script reads closer to plain language, with less visual noise between the story and the structure.

Story structure

A story is organized into beats, named sections that work like scenes or chapters. A beat can jump to another with ->, or call a sub-beat with BeatName() which returns to the caller when finished:

beat EnterCafe
  The morning sun streams through the café windows.
  barista: Welcome! I don't think I've seen you here before.
  TakeOrder()
  barista: Enjoy your coffee!
  -> EndScene

beat TakeOrder
  barista: So, what can I get started for you?

Characters are a first-class concept: you declare them with fields that can be read and updated throughout the story:

character barista
  name: Alex
  mood: friendly
  friendship: 0

Persistent data lives in a state block. Conditionals (if, else if, else) let the story react to earlier decisions, and choices can have inline conditions to show or hide options dynamically.

Other features

Beyond the core syntax, Loreline also supports:

Platforms and integration

Loreline is written in Haxe and compiles natively to multiple targets. The API works the same way everywhere: load a Loreline script, then play it by providing callbacks for dialogue, choices, and story completion. All targets share a common codebase, which ensures consistent behaviour across languages and lets updates ship everywhere at once with no major feature differences. A suite of over 160 test files covers the runtime to help keep it reliable as the language evolves.

Platform Notes
JavaScript / TypeScript Works in Node.js and the browser. Available as an npm package.
C# / .NET Integrates with Unity, Godot (.NET), and any .NET project.
C++ Standalone library with no external dependencies.
Python Works with Python 3.8+, no external dependencies.
Lua Works with Lua 5.1+, no external dependencies.
Haxe Available as a haxelib package with full API access.

See the integration guides for setup instructions for each platform.

Tooling

Comparison with Ink and Yarn Spinner

Ink and Yarn Spinner are the two other tools commonly used for interactive narrative. Here's how they compare.

This section tries to make an honest comparison, as of when this article was written, and is in no way claiming one tool is better or worse than the others. I believe there is room for diversity in software for writing interactive fiction. Loreline is just joining the party!

Ink was created by Inkle and has been used in games like 80 Days and Heaven's Vault. Its structure is built around knots (major sections), stitches (sub-sections), and a weave system for inline branching. Flow control uses diverts (-> knot_name), and choices use * (once-only) or + (sticky). Ink has a mature C# runtime with an official Unity plugin. A JavaScript port (inkjs) is also officially supported; community C++ ports exist as well, including inkcpp (a standalone library also available as a UE plugin) and Inkpot (an UE5 plugin by The Chinese Room). Ink has no built-in localization system, so developers typically build their own tooling on top of the #tag metadata.

Yarn Spinner uses a screenplay-like format organized into nodes, each delimited by --- and ===. Commands use <<double angle brackets>> and options -> (v2+). It has strong Unity integration and a visual node editor for building dialogue graphs. It is primarily a C#/.NET tool; an official C++ implementation for Unreal Engine is in active development (beta as of early 2026).

Here is the same short script written in all three languages: narration, dialogue, a choice with a conditional option, and a variable update:

state
  coffeesOrdered: 0

character barista
  name: Alex

beat CoffeeShop
  The warm aroma of coffee fills the café.
  barista: Hi there! What can I get you?

  choice
    A regular coffee
      barista: Coming right up!

    A decaf if coffeesOrdered > 2
      barista: Switching to decaf? Probably wise.

  coffeesOrdered += 1
  if coffeesOrdered > 3
    barista: That's quite a lot of coffee today...
VAR coffeesOrdered = 0

=== CoffeeShop ===
The warm aroma of coffee fills the café.
Alex: Hi there! What can I get you?

+ A regular coffee
  Alex: Coming right up!
+ {coffeesOrdered > 2} A decaf
  Alex: Switching to decaf? Probably wise.
-
~ coffeesOrdered = coffeesOrdered + 1
{coffeesOrdered > 3:
  Alex: That's quite a lot of coffee today...
}
-> DONE
title: CoffeeShop
---
<<declare $coffeesOrdered = 0>>

The warm aroma of coffee fills the café.
Alex: Hi there! What can I get you?

-> A regular coffee
    Alex: Coming right up!
-> A decaf <<if $coffeesOrdered > 2>>
    Alex: Switching to decaf? Probably wise.

<<set $coffeesOrdered = $coffeesOrdered + 1>>
<<if $coffeesOrdered > 3>>
    Alex: That's quite a lot of coffee today...
<<endif>>
===

All three scripts tell the same story with comparable length but different conventions. Each language has its own way of handling declarations, choices, and branching.

Detailed comparison

Loreline Ink Yarn Spinner
Structure beat sections with indentation; nested choices and alternatives Knots (major sections) + stitches (sub-sections) + weave for inline branching Named nodes, each delimited by --- / ===
Choices choice keyword + indentation; inline if conditions on individual options * (disappears once chosen) or + (always available); the marker controls whether each option reappears; weave handles local branching -> options with <<if>> conditions
Characters First-class: declared with typed fields, readable and writable throughout the story Handled by the host app Handled by the game engine
State Variables live in state blocks: top-level state (shared across the whole script); state inside a beat (scoped to that beat); new state (temporary, reset each time the beat runs) Built-in variables: VAR (global), TEMP (local), LIST (enum-like) Built-in variables; declared with <<declare>>
Text tags Built-in <tag> on any dialogue line; passed to the host as-is without affecting flow #tag appended to output text <<command>> blocks for game instructions
Alternatives Built-in with 5 modes: sequence, cycle, once, pick, shuffle Sequences and shuffle via & / ~ choice markers Not built-in; handled through custom scripting
Functions 60+ built-in functions. Custom functions can be written directly in Loreline scripts Built-in functions for common operations; custom logic requires external functions written in C# or JavaScript Custom logic handled through C# commands and functions registered from the host app
Localization Built-in: #key tags in-script + .lor translation files per language Not built-in; teams typically build on #tag metadata CSV string table export/import
Targets JS, TS, C#, C++, Python, Lua, Haxe, all from a single Haxe codebase C# official (Unity); JS official (inkjs); C++ community only (inkcpp, Inkpot) C# / .NET official (Unity); C++ for UE in active development (beta)

Ink and Yarn Spinner are mature, well-documented, and have been used in shipped games for years. Loreline is newer and takes a different approach: indentation-based structure, characters and state as core language features, and native compilation to multiple platforms from a single codebase. The best choice comes down to team familiarity, target engine, and which scripting style fits how you want to write.

Try Loreline

The fastest way to get started is the Playground, where you can write and run Loreline scripts directly in the browser, no setup needed. When you're ready to go further, the Writer's Guide covers the full language step by step.