Getting started
Brass Lantern is a small engine for classic parser text adventures (go north, take lamp, give the mug to Gary) that runs in the browser inside a CRT terminal. You write a game as data; the engine runs it.
It also plays Z-machine story files, the format Infocom’s games shipped in, in the same terminal.
It comes with five cartridges: Snack Attack, a three-room world that exists to show the engine off, Zork I, II and III, and Zork I rebuilt natively on the engine. Play them, then come back. For a full-length game built on the engine, try Office Space: The Text Adventure.
Use it in your app
Brass Lantern is on npm as three packages: the engine, a Vue terminal and an optional intent server. Using the library covers installing them, mounting a game, themes, running headless in Node and wiring the intent server.
npm i @brass-lantern/engine @brass-lantern/vue vue piniaRun the demo from the repo
You need Node 24 or later.
git clone https://github.com/mrballistic/brass-lantern.git
cd brass-lantern
npm install
npm run dev # http://localhost:5173The terminal boots to the cartridge menu; type 1 for Snack Attack, 2–4 for Zork I–III, or 5 for the native Zork I. All are fully playable offline: the regex parser handles every command the engine knows, and Zork has its own parser. Loose phrasing (“make that thing stop beeping”) needs the optional intent server.
What’s in the box
packages/
engine/ @brass-lantern/engine: the engine, the regex parser, fuzzy matching,
line styling, createGame, auditWorld, the story-file runtime
(zmachine/), and the bundled worlds (worlds/)
vue/ @brass-lantern/vue: the terminal, the boot sequence, the game stores,
themes, mountGame, and the CRT stylesheet
server/ @brass-lantern/server: the intent server (parseIntent, intentRoute),
plus the small Express app the repo runs (src/index.ts)
apps/
site/ the demo: app.config.ts lists the cartridges, main.ts mounts the game,
apps/site/public/stories/ holds the Zork trilogy and its licenses
docs/ this site (npm run docs:dev)
scripts/ the consumer smoke test, the glkapi vendoringEach package has its own tests; packages/engine/src/types/world.ts is the world schema, with a comment on every field.
Make it yours
In your own app, the cartridges are just the cartridges you pass to mountGame (Using the library). In the repo, everything that makes the demo a particular game collection is in apps/site/src/app.config.ts:
export const cartridges: Cartridge[] = [
{ kind: 'world', id: 'snack-attack', title: 'SNACK ATTACK', world: tutorial },
{ kind: 'zcode', id: 'zork1', title: 'ZORK I', story: 'stories/zork1.z3', format: 'Z-machine v3' },
{ kind: 'zcode', id: 'zork2', title: 'ZORK II', story: 'stories/zork2.z3', format: 'Z-machine v3' },
{ kind: 'zcode', id: 'zork3', title: 'ZORK III', story: 'stories/zork3.z3', format: 'Z-machine v3' },
{ kind: 'world', id: 'zork1-native', title: 'ZORK I · NATIVE', world: zork1 },
];
export const appName = 'BRASS LANTERN';
export const storagePrefix = 'brass-lantern';Add your own world as a cartridge (and remove the others if you like: with one cartridge there’s no menu). Building worlds walks through writing one, from two rooms up; Playing story files covers Z-machine cartridges.
Working on Brass Lantern itself? CONTRIBUTING.md has the checks to run before a pull request.