A headless interactive-fiction engine for TypeScript.
The dullahan is the headless horseman of Irish myth — it rides carrying its own head. This engine is headless too: it holds the world and hands you the state; you bring the head (the rendering).
A port of okaybenji/text-engine. The original renders a text-adventure directly to the DOM via println. dullahan keeps the same tiny, JS-object "disk" world model — rooms, items, characters, topics, inventory, state — but instead of printing, execute(command) returns a structured EngineView:
import {Engine} from '@bodar/dullahan';
const engine = new Engine(myDisk); // myDisk = a factory returning the world object
let view = engine.view(); // initial room
view = engine.execute('go north');
// view.roomId — where you are now
// view.title — room name
// view.description — room description
// view.exits — [{label, roomId}] available directions
// view.items — objects in the room
// view.characters — people in the room
// view.messages — output lines from the command just run
// view.suggestions — { verb: [nouns] } available actions, derived from state
// view.conversation — active dialogue topics, if talking to someoneThe consumer decides how to render the view — terminal, cards, JSON API, anything.
- Headless: returns data, never touches the DOM. Runs in the browser, Bun, Node, or a Worker.
- Available actions:
view.suggestionsenumerates the valid moves from the current state (which exits work, which topics can be discussed, what can be taken/used) — ideal for a command-suggestion UI. - Typed: the disk schema is fully typed.
The disk format is compatible with the original text-engine (see src/types.ts). A disk is a factory function returning {roomId, rooms, inventory?, characters?}.
This is a derivative work of okaybenji/text-engine by Benji Kay, licensed under the GNU General Public License v3.0. This port is likewise GPL-3.0-or-later — see LICENSE.