Getting Started
Installation
npm install @lsde/dialog-enginedotnet add package LsdeDialogEngine
# JSON loader — choose ONE based on your platform:
dotnet add package LsdeDialogEngine.Newtonsoft # Unity
dotnet add package LsdeDialogEngine.SystemTextJson # .NET 5+ / Godot .NET# Add as a git submodule
git submodule add https://github.com/jonlepage/LS-Dialog-Editor-Engine.git deps/lsde
# CMakeLists.txt
add_subdirectory(deps/lsde/lsde-cpp)
target_link_libraries(your_target PRIVATE lsde)# Copy the headers and source into your project
# e.g. Source/YourModule/ThirdParty/lsde/
cp -r lsde-cpp/include/lsde YourProject/Source/YourModule/ThirdParty/lsde/include
cp -r lsde-cpp/src/* YourProject/Source/YourModule/ThirdParty/lsde/src
# In your module's .Build.cs, add:
# PublicIncludePaths.Add(Path.Combine(ModuleDirectory, "ThirdParty/lsde/include"));
# Add the .cpp files from ThirdParty/lsde/src/ to your module# Clone the repository
git clone https://github.com/jonlepage/LS-Dialog-Editor-Engine.git
# Copy the addon into the Godot project
mkdir -p addons
cp -r LS-Dialog-Editor-Engine/lsde-gdscript/addons/lsde addons/lsde
rm -rf LS-Dialog-Editor-EngineMinimal Usage
The engine is a graph traversal machine — it dispatches blocks to registered handlers, which give them meaning. Without handlers, the engine has no output.
Format-agnostic
The engine consumes a BlueprintExport object, not a file. You can load your blueprint from JSON, XML, or YAML using any parser suited to your platform. See Parsing & Import for recommendations.
import { DialogueEngine } from '@lsde/dialog-engine';
import blueprintJson from './blueprints.json';
const engine = new DialogueEngine();
engine.init({ data: blueprintJson });
// Unified condition resolver — evaluates game-state conditions for both
// choice visibility and condition block pre-evaluation.
engine.onResolveCondition((cond) => game.evaluateCondition(cond));
// 3 required handlers — bridge between the engine and your game
// (onCondition is optional when onResolveCondition is installed)
engine.onDialog(({ scene, block, context, next }) => {
game
.createDialogAuto(block, context)
.catch(() => scene.cancel())
.finally(() => next());
});
engine.onChoice(({ scene, block, context, next }) => {
game
.createChoiceAuto(block, context)
.catch(() => scene.cancel())
.finally(() => next());
});
engine.onAction(({ block, context, next }) => {
game
.executeActions(block.actions)
.catch((err) => context.reject(err))
.finally(() => next());
});
// Start a scene anywhere in your game code
function myGameScript(sceneId: string) {
const scene = engine.scene(sceneId);
scene.start();
}using LsdeDialogEngine;
using LsdeDialogEngine.Json; // LsdeDialogEngine.SystemTextJson package
// Unity: use LsdeDialogEngine.Newtonsoft instead
var blueprint = LsdeJson.Parse(File.ReadAllText("blueprints.json"));
var engine = new DialogueEngine();
engine.Init(new InitOptions { Data = blueprint });
// Unified condition resolver — evaluates game-state conditions for both
// choice visibility and condition block pre-evaluation.
engine.OnResolveCondition(cond => Game.EvaluateCondition(cond));
// 3 required handlers — bridge between the engine and your game
engine.OnDialog(args => {
var (scene, block, context, next) = args;
Game.ShowDialog(block, context, onComplete: next);
});
engine.OnChoice(args => {
var (scene, block, context, next) = args;
Game.ShowChoices(block, context, onSelected: next);
});
engine.OnAction(args => {
var (scene, block, context, next) = args;
Game.ExecuteActions(block.Actions);
context.Resolve();
next();
});
// Start a scene anywhere in your game code
void MyGameScript(string sceneId) {
var scene = engine.Scene(sceneId);
scene.Start();
}#include <lsde/engine.h>
using namespace lsde;
DialogueEngine engine;
engine.init({blueprint});
// Unified condition resolver — evaluates game-state conditions for both
// choice visibility and condition block pre-evaluation.
engine.onResolveCondition([](const ConditionTest& cond) {
return game->evaluateCondition(cond);
});
// 3 required handlers — bridge between the engine and your game
engine.onDialog([](auto* scene, auto* block, auto* ctx, auto next) -> CleanupFn {
game->showDialog(block, ctx, [next]() { next(); });
return {};
});
engine.onChoice([](auto* scene, auto* block, auto* ctx, auto next) -> CleanupFn {
game->showOptions(block, ctx, [next]() { next(); });
return {};
});
engine.onAction([](auto*, auto* block, auto* ctx, auto next) -> CleanupFn {
game->executeActions(block->actions);
ctx->resolve();
next();
return {};
});
// Start a scene anywhere in your game code
auto scene = engine.scene(sceneId);
scene->start();var engine = LsdeDialogueEngine.new()
engine.init({"data": blueprint})
# Unified condition resolver — evaluates game-state conditions for both
# choice visibility and condition block pre-evaluation.
engine.on_resolve_condition(func(cond):
return game.evaluate_condition(cond)
)
# 3 required handlers — bridge between the engine and your game
engine.on_dialog(func(args):
await game.show_dialog(args["block"], args["context"])
args["next"].call()
)
engine.on_choice(func(args):
await game.show_options(args["block"], args["context"])
args["next"].call()
)
engine.on_action(func(args):
game.execute_actions(args["block"].actions)
args["context"].resolve()
args["next"].call()
)
# Start a scene anywhere in your game code
func my_game_script(scene_id: String):
var scene = engine.scene(scene_id)
scene.start()Blueprint Validation
engine.init() returns a diagnostic report with errors, warnings, and stats. The check option cross-validates against the host application's capabilities:
engine.init({
data,
check: {
functions: ['set_flag', 'play_sound'], // function ids your game implements
dictionaries: { items: ['sword', 'shield'] }, // dictionary ids and their entry keys
cards: ['Alice', 'Bob'], // card NAMES, never the editor id
},
});engine.Init(new InitOptions {
Data = blueprint,
Check = new CheckOptions {
Functions = new() { "set_flag", "play_sound" },
Dictionaries = new() { ["items"] = new() { "sword", "shield" } },
Cards = new() { "Alice", "Bob" },
},
});lsde::CheckOptions check;
check.functions = {"set_flag", "play_sound"};
check.dictionaries = {{"items", {"sword", "shield"}}};
check.cards = {"Alice", "Bob"};
engine.init({blueprint, check});engine.init({
"data": blueprint,
"check": {
"functions": ["set_flag", "play_sound"],
"dictionaries": {"items": ["sword", "shield"]},
"cards": ["Alice", "Bob"],
},
})The twenty-five diagnostics
Errors — the payload is refused and nothing plays. errors is non-empty and engine.scene() has nothing to hand you.
| Code | What happened |
|---|---|
MISSING_DATA | No data was passed to init() |
MISMATCHED_EXPORTS | Several files merged that come from different exports — project or exportedAt disagree. Pass the files of ONE export |
WRONG_NAMING_CONVENTION | Exported in snake_case or PascalCase; the engine reads camelCase. Project settings › Exporters › Naming convention |
INVALID_FORMAT | format is not lsde-blueprints. It is also what C# and C++ report for the case above: they validate a typed object, so the original key names are already gone |
UNSUPPORTED_FORMAT_VERSION | version is not 1. A project still on LSDE 1.6 belongs on engine 0.3.x — there is no dual reader |
NO_SCENES | The payload carries no scene |
DUPLICATE_SCENE | Two scenes share a path or a stable id |
MISSING_SCENE_PATH | A scene has no path |
DUPLICATE_BLOCK_ID | Two blocks of the SAME scene share an id. Across scenes it is legal and expected — a block is (scene, id) |
INVALID_START_BLOCK | The scene names a start block that is not one of its blocks |
BROKEN_LINK | A wire points at a block that is not in the scene. The traversal would simply have nowhere to go |
Warnings — it plays, and something will quietly not work. Read them; none of them is noise.
| Code | What it costs you |
|---|---|
NO_START_BLOCK | The scene has no start block, so start() has nowhere to begin |
UNKNOWN_WAIT_BLOCK | A waitForBlocks id is not a block of the scene, so that track parks for good. The check cannot go further: an id that does exist may still never be played |
EMPTY_FUNCTION | An action has a call with no function picked. The game is handed a call it cannot run |
UNDECLARED_FUNCTION | An action calls a function the export does not declare — a v1 id left behind in the project, say. It used to load silently and fail in game |
UNDECLARED_ARGUMENT | A call passes an argument the function does not declare |
UNDECLARED_DICTIONARY_KEY | A dictionaryKey argument is not an entry of the dictionary its parameter names |
UNDECLARED_DICTIONARY | A condition tests a dictionary the export does not declare |
UNDECLARED_ENTRY | A condition tests an entry its dictionary does not declare |
UNKNOWN_CHOICE_BLOCK | A test on the reserved choice dictionary names a block that is not a CHOICE of this scene |
UNKNOWN_CHOICE_OPTION | A test on the reserved choice dictionary names an option that CHOICE does not have |
UNKNOWN_FUNCTION | An action calls a function id your check.functions does not list |
UNKNOWN_DICTIONARY | A condition tests a dictionary id your check.dictionaries does not list |
UNKNOWN_DICTIONARY_ENTRY | The dictionary is known, the entry key is not |
UNKNOWN_CARD | A block cites an actor card NAME your check.cards does not list |
The last four only appear when you pass check — without it the engine has nothing to compare against. The eight above them are always on: they compare what the blocks USE with what the export itself DECLARES, so no check is needed.
A diagnostic found in a scene carries sceneId — the stable id (sc_…), what handle.getSceneId() answers — and scenePath, plus blockId when it points at a block.
