@brass-lantern/mcp 0.0.1-stage → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +37 -0
  3. package/dist/cli.d.ts +2 -0
  4. package/dist/cli.js +19 -0
  5. package/dist/docs/llms.txt +35 -0
  6. package/dist/docs/pages/guide/building-worlds/index.md +218 -0
  7. package/dist/docs/pages/guide/building-worlds/recipes.md +1406 -0
  8. package/dist/docs/pages/guide/building-worlds/two-rooms.md +339 -0
  9. package/dist/docs/pages/guide/coding-agents.md +124 -0
  10. package/dist/docs/pages/guide/deploying.md +56 -0
  11. package/dist/docs/pages/guide/getting-started.md +72 -0
  12. package/dist/docs/pages/guide/how-it-works.md +152 -0
  13. package/dist/docs/pages/guide/intent-server.md +92 -0
  14. package/dist/docs/pages/guide/porting-zork.md +115 -0
  15. package/dist/docs/pages/guide/testing.md +100 -0
  16. package/dist/docs/pages/guide/using-the-library.md +272 -0
  17. package/dist/docs/pages/guide/your-first-world.md +206 -0
  18. package/dist/docs/pages/guide/z-machine.md +96 -0
  19. package/dist/docs/pages/index.md +58 -0
  20. package/dist/docs/pages/reference/cartridges.md +47 -0
  21. package/dist/docs/pages/reference/commands.md +79 -0
  22. package/dist/docs/pages/reference/conditions-and-events.md +129 -0
  23. package/dist/docs/pages/reference/world-schema.md +592 -0
  24. package/dist/docs/pages.json +74 -0
  25. package/dist/docs.d.ts +21 -0
  26. package/dist/docs.js +89 -0
  27. package/dist/engine.d.ts +16 -0
  28. package/dist/engine.js +42 -0
  29. package/dist/job.d.ts +22 -0
  30. package/dist/job.js +29 -0
  31. package/dist/load.d.ts +22 -0
  32. package/dist/load.js +106 -0
  33. package/dist/run.d.ts +16 -0
  34. package/dist/run.js +57 -0
  35. package/dist/server.d.ts +47 -0
  36. package/dist/server.js +97 -0
  37. package/dist/stdio.d.ts +25 -0
  38. package/dist/stdio.js +75 -0
  39. package/dist/tools.d.ts +8 -0
  40. package/dist/tools.js +82 -0
  41. package/dist/worker.d.ts +1 -0
  42. package/dist/worker.js +6 -0
  43. package/package.json +47 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Todd Greco
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,37 @@
1
+ <p><img src="https://raw.githubusercontent.com/mrballistic/brass-lantern/main/docs/public/brand/lantern-mark-amber.svg" alt="" width="72" height="72"></p>
2
+
3
+ # Brass Lantern MCP server
4
+
5
+ `@brass-lantern/mcp`: a local [Model Context Protocol](https://modelcontextprotocol.io) server that lets a coding agent write, audit and play a Brass Lantern world, and read the docs for the version it is using.
6
+
7
+ ## Install
8
+
9
+ In Claude Code:
10
+
11
+ ```bash
12
+ claude mcp add brass-lantern -- npx -y @brass-lantern/mcp
13
+ ```
14
+
15
+ Or in any client’s JSON config:
16
+
17
+ ```json
18
+ { "mcpServers": { "brass-lantern": { "command": "npx", "args": ["-y", "@brass-lantern/mcp"] } } }
19
+ ```
20
+
21
+ Node 24 or later. World paths are relative to where the client starts the server. Worlds must be inside that directory or one you allow with `--root <dir>` (repeatable). Each `audit_world` and `play` call runs in its own worker thread and is stopped after 10 seconds; `--timeout <seconds>` changes that.
22
+
23
+ ## Tools
24
+
25
+ - `audit_world`: checks a world file for broken exits, bad conditions, missing references and more.
26
+ - `play`: types commands into a fresh, seeded game and returns the transcript and final state.
27
+ - `docs`: the documentation for this version, as an index, a page, or a search.
28
+
29
+ It uses the world’s own project’s engine when it has one, and every reply says which engine ran.
30
+
31
+ ## Safety
32
+
33
+ It runs the world module it is pointed at, with your permissions, like your test runner. It is local stdio only and makes no network calls. The allowed-directories check confines which world file is loaded, not what that module imports or does: any module inside an allowed directory can be run. It is not a sandbox.
34
+
35
+ ## Docs
36
+
37
+ [Coding agents](https://mrballistic.github.io/brass-lantern/guide/coding-agents) covers the tools, the engine choice and the limits in full. Source and issues: [github.com/mrballistic/brass-lantern](https://github.com/mrballistic/brass-lantern). MIT licensed.
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ // The stdio entry point an MCP client launches (`npx -y @brass-lantern/mcp`). Nothing may be
3
+ // written to stdout except through the transport: it carries the protocol.
4
+ import { readFileSync } from 'node:fs';
5
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
6
+ import { createServer } from "./server.js";
7
+ import { keepRunningOnCrash, parseRoots, parseTimeout, quietStdout } from "./stdio.js";
8
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
9
+ // Each audit_world and play runs in its own worker (stdout piped to stderr); these two are a
10
+ // second layer, for anything that still runs on the main thread.
11
+ quietStdout();
12
+ // A world's late throw (a timer, a stray promise) is logged to stderr instead of ending the server.
13
+ keepRunningOnCrash();
14
+ // --root <dir> (repeatable) allows worlds from another directory besides the launch directory.
15
+ const roots = parseRoots(process.argv.slice(2), process.cwd());
16
+ // --timeout <seconds> bounds one audit_world or play call (default 10).
17
+ const timeoutMs = parseTimeout(process.argv.slice(2));
18
+ const server = createServer({ roots, cwd: process.cwd(), timeoutMs, timeoutHint: 'Raise the limit with --timeout <seconds>.', version: pkg.version });
19
+ await server.connect(new StdioServerTransport());
@@ -0,0 +1,35 @@
1
+ # Brass Lantern
2
+
3
+ > An npm library for classic parser text adventures (go north, take lamp) in a CRT terminal. Worlds are data: rooms, items, characters, rules and events in one typed object. It also plays Z-machine story files such as Zork.
4
+
5
+ The whole documentation in one file: [llms-full.txt](https://mrballistic.github.io/brass-lantern/llms-full.txt)
6
+
7
+ ## Start here
8
+
9
+ - [Home](https://mrballistic.github.io/brass-lantern/index.md): Three library packages for classic parser games in a CRT terminal, plus an MCP server for coding agents. Write your own world as data, mount it on a page, or load a Z-machine story file. The demo plays the Zork trilogy.
10
+
11
+ ## Guide
12
+
13
+ - [Getting started](https://mrballistic.github.io/brass-lantern/guide/getting-started.md): 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.
14
+ - [Using the library](https://mrballistic.github.io/brass-lantern/guide/using-the-library.md): Brass Lantern is three library packages, plus an MCP server for coding agents (Coding agents). Use the libraries to put a game in your own app, run one headless in Node, or add the intent server to a backend you already have.
15
+ - [Coding agents](https://mrballistic.github.io/brass-lantern/guide/coding-agents.md): Two ways to give a coding agent (Claude Code, Cursor, Codex and the like) what it needs to build a Brass Lantern world: the docs as one file, and a local MCP server that can also audit and play the world it writes.
16
+ - [Playing story files (Zork)](https://mrballistic.github.io/brass-lantern/guide/z-machine.md): Brass Lantern also runs **Z-machine story files**, the format Infocom’s games shipped in. The demo includes the **Zork trilogy** (Zork I Release 119, Zork II Release 63, Zork III Release 25), which Microsoft released under the MIT License in 2025. It plays exactly as Infocom wrote it, in the same CRT terminal as native worlds. The npm packages bring the interpreter, not the games: see Cartridges for adding story files to your own app.
17
+ - [Porting Zork](https://mrballistic.github.io/brass-lantern/guide/porting-zork.md): Brass Lantern ships Zork I twice: the original story file, run by the Z-machine interpreter, and **ZORK I · NATIVE**, a rebuild as an ordinary Brass Lantern world (`packages/engine/src/worlds/zork1.ts`). The native version is how the engine proves it can carry an Infocom-class game. A test plays both side by side and fails if they disagree.
18
+ - [How it works](https://mrballistic.github.io/brass-lantern/guide/how-it-works.md): Three parts, kept deliberately apart, and shipped as three library packages (Using the library), plus an MCP server for coding agents (Coding agents): the engine and parser are `@brass-lantern/engine`, the terminal and stores are `@brass-lantern/vue`, and the intent server is `@brass-lantern/server`. The engine imports no Vue, Pinia or browser globals, so the same turn loop runs in a browser, in Node (`createGame`) and in tests.
19
+ - [The intent server](https://mrballistic.github.io/brass-lantern/guide/intent-server.md): Players don’t type like parsers. The intent server hears what the regex parser couldn’t handle, such as “make that thing stop beeping” or “pocket my billfold”, and asks an LLM which of the engine’s verbs and IDs that means. It’s optional: without it the game is fully playable, and loose phrasing just gets a “didn’t understand” reply.
20
+ - [Deploying](https://mrballistic.github.io/brass-lantern/guide/deploying.md): A Brass Lantern game is a static site, plus the intent server if you want loose phrasing understood. If you use the packages in your own app (Using the library), deploy your app the way you already do and mount `intentRoute` in your backend; this page is about the repo’s demo site and server.
21
+
22
+ ## Building worlds
23
+
24
+ - [Overview](https://mrballistic.github.io/brass-lantern/guide/building-worlds/index.md): A world is one TypeScript object, typed by `World`. There’s no build step, no scripting language and no engine code to write: rooms, items, people and rules are data, and the engine plays them.
25
+ - [A two-room game](https://mrballistic.github.io/brass-lantern/guide/building-worlds/two-rooms.md): This page builds the smallest complete game, `packages/engine/src/worlds/examples/two-rooms.ts`, from nothing. You’re home after a long time away; the door is locked, and you left the key somewhere obvious. A test plays it to the end.
26
+ - [The demo game: Snack Attack](https://mrballistic.github.io/brass-lantern/guide/your-first-world.md): A world is one TypeScript object: rooms, items, people, the lines they say, and the rules that join them. If you haven’t written one yet, start with a two-room game; this page goes further, and builds **Snack Attack**, the tutorial world (`tutorial` in `@brass-lantern/engine/worlds`, source on GitHub), piece by piece. A test plays that exact file to the end, so everything here matches what the engine does.
27
+ - [Recipes](https://mrballistic.github.io/brass-lantern/guide/building-worlds/recipes.md): Each recipe is a small, complete world about one idea, in `packages/engine/src/worlds/examples/`. Every transcript on this page is what a test gets by typing its commands into that world.
28
+ - [Testing a world](https://mrballistic.github.io/brass-lantern/guide/testing.md): A world is data, and data rots quietly: an exit points at a renamed room, a flag label gets a typo, the ending becomes unreachable. Two kinds of test catch nearly all of it, and both run in plain Node with any test runner. The examples here use Vitest.
29
+
30
+ ## Reference
31
+
32
+ - [World schema](https://mrballistic.github.io/brass-lantern/reference/world-schema.md): Every field a world can use. The source of truth is `packages/engine/src/types/world.ts`, which has a comment on each one. “Condition” means a condition string; “event” means a key in `events`.
33
+ - [Conditions and events](https://mrballistic.github.io/brass-lantern/reference/conditions-and-events.md): Conditions are strings, used by `requires`, exits (`if`, `denials`), room `descriptions`, `onEnter`, rules (`instead`, `after`, `onUse`), dialogue keys, hints, scoring, daemons, the finale’s epilogue and ambient lines. One parser handles all of them. A missing or blank condition always holds, so a trigger (`onEnter` and the like) may leave out `if`.
34
+ - [Player commands](https://mrballistic.github.io/brass-lantern/reference/commands.md): The built-in verbs are fixed; a world gives them things to act on, and can add verbs of its own. Anything the regex parser doesn’t recognize, or recognizes but can’t act on, can go to the intent server, which maps loose phrasing onto these.
35
+ - [Cartridges and storage](https://mrballistic.github.io/brass-lantern/reference/cartridges.md): Cartridges are what you pass in `GameOptions.cartridges` (see Using the library). The types, `Cartridge`, `WorldCartridge` and `ZCodeCartridge`, are exported from both `@brass-lantern/engine` and `@brass-lantern/vue`.
@@ -0,0 +1,218 @@
1
+ # Building worlds
2
+
3
+ A world is one TypeScript object, typed by [`World`](https://mrballistic.github.io/brass-lantern/reference/world-schema). There’s no build step, no scripting language and no engine code to write: rooms, items, people and rules are data, and the engine plays them.
4
+
5
+ This section is three worked examples, each a real world file that a test plays from start to finish, so the code you read here is code that runs:
6
+
7
+ - **[A two-room game](https://mrballistic.github.io/brass-lantern/guide/building-worlds/two-rooms)** starts from nothing: two rooms, a hidden key, a locked door and a way to win. Start here.
8
+ - **[The demo game: Snack Attack](https://mrballistic.github.io/brass-lantern/guide/your-first-world)** walks through the three-room world the demo boots into, with people, dialogue, hints, a score and a finale.
9
+ - **[Recipes](https://mrballistic.github.io/brass-lantern/guide/building-worlds/recipes)** are small worlds for one idea each: containers and keys, darkness and death, timers, endings with verbs of your own, a guard to fight, scripts, a librarian with topics, a wandering cat, a room that listens, a raft, and a robot, a dial and a dune buggy.
10
+
11
+ Every one of them ships with the engine, so you can play it, read it or start from it: `import { twoRooms, tutorial, containers } from '@brass-lantern/engine/worlds'`.
12
+
13
+ ## Where a world lives
14
+
15
+ Anywhere in your app. Put it in a file of its own, typed with `World`:
16
+
17
+ ```ts
18
+ // my-world.ts
19
+ import type { World } from '@brass-lantern/engine';
20
+
21
+ export const myWorld: World = {
22
+ startRoom: 'porch',
23
+ rooms: { /* … */ },
24
+ items: { /* … */ },
25
+ npcs: {},
26
+ dialogue: {},
27
+ events: { intro: [ /* … */ ] },
28
+ flagLabels: {},
29
+ };
30
+ ```
31
+
32
+ Then hand it to the terminal as a cartridge ([Using the library](https://mrballistic.github.io/brass-lantern/guide/using-the-library) has the setup):
33
+
34
+ ```ts
35
+ import { mountGame } from '@brass-lantern/vue';
36
+ import '@brass-lantern/vue/style.css';
37
+ import { myWorld } from './my-world';
38
+
39
+ mountGame('#app', {
40
+ cartridges: [{ kind: 'world', id: 'my-world', title: 'MY WORLD', world: myWorld }],
41
+ storagePrefix: 'my-world',
42
+ });
43
+ ```
44
+
45
+ With one cartridge the terminal boots straight into it; with several it shows a menu.
46
+
47
+ ## The loop
48
+
49
+ 1. **Write a little.** One room, one item, one rule.
50
+ 2. **Play it** in the browser, or in a test (below), which is faster.
51
+ 3. **Run the checks.** `auditWorld(myWorld)` lists effects naming things that don’t exist, exits to nowhere, unknown conditions and events, and IDs, flags or variables named `__proto__`, `constructor` or `prototype` (the engine ignores those names, so they can’t reach `Object.prototype`; a player who types one just gets the reply for a word the game doesn’t know). Those mistakes fail silently in play, so make them fail loudly in a test: expect it to return `[]`.
52
+
53
+ A test that plays your world is a few lines:
54
+
55
+ ```ts
56
+ import { expect, it } from 'vitest';
57
+ import { auditWorld, createGame } from '@brass-lantern/engine';
58
+ import { twoRooms } from '@brass-lantern/engine/worlds';
59
+
60
+ it('has no broken references', () => {
61
+ expect(auditWorld(twoRooms)).toEqual([]);
62
+ });
63
+
64
+ it('can be won', () => {
65
+ const game = createGame(twoRooms, { seed: 1 });
66
+ for (const line of ['examine mat', 'take key', 'unlock door with key', 'open door', 'north', 'read letter']) game.send(line);
67
+ expect(game.state.gameOver).toBe(true);
68
+ });
69
+ ```
70
+
71
+ [Testing a world](https://mrballistic.github.io/brass-lantern/guide/testing) covers the rest.
72
+
73
+ ## How names are matched
74
+
75
+ The player’s words find a thing (or a character, or a topic) by trying these, strongest first, and taking the best that finds anything:
76
+
77
+ 1. **exactly** its name, an alias or its ID (`take brass lantern`);
78
+ 2. **a whole word** of its name or alias (`take lantern`);
79
+ 3. **a whole word of its ID**, or **part of** its name (`take lant`);
80
+ 4. **part of** its ID;
81
+ 5. **word starts**: every word of three or more letters the player typed matches the start of a word in its name, aliases or ID (`take lantern of brass`). Shorter words (`a`, `of`, `my`) are ignored, and so are common determiners and possessives (`this`, `that`, `his`, `your`, `some`, …): the player may type them, and they never have to match;
82
+ 6. in brass style only, **a close misspelling**: a typed word of five or more letters that is at least 80% alike (the start of a longer word counts too), so `lunberg` finds Lumbergh. Among misspellings, a close match to a whole word of the name beats one found only through an ID word or a word’s start.
83
+
84
+ Objects and people are compared together, so `examine lumbergh` finds Lumbergh rather than his mug. On a tie, EXAMINE prefers objects; ATTACK, FOLLOW and the person in GIVE/SHOW … TO prefer people; other verbs look at objects first. If two things match equally well, the game asks “Which do you mean?”.
85
+
86
+ Infocom style never guesses misspellings, and neither do turns the intent server answers. In brass style a world can tease the player, once per thing, with [`typoReplies`](https://mrballistic.github.io/brass-lantern/reference/world-schema#world).
87
+
88
+ The word-start step is strict on purpose: **a word the world doesn’t know stops the match.** If the player types `take red ball` and the world calls the thing only “ball”, nothing matches, even though there is a ball right there (in brass style, a close misspelling of a word that *is* there still matches):
89
+
90
+ ```
91
+ > take red ball
92
+ You don’t see a “red ball” here.
93
+ ```
94
+
95
+ So when your description gives a thing a color, a size or a material, put that word in its name or an alias. Either of these works:
96
+
97
+ ```ts
98
+ ball: { name: 'ball', aliases: ['red ball'], description: 'A red rubber ball.', portable: true, tags: [] },
99
+ ball: { name: 'red ball', description: 'A red rubber ball.', portable: true, tags: [] },
100
+ ```
101
+
102
+ ```
103
+ > take red ball
104
+ Taken: ball.
105
+ ```
106
+
107
+ (Before 2.1.0 one matching word was enough, so `give smiley flair` could land on some *other* flair once the smiley one was gone. If a test of yours stops finding a thing after upgrading, this is why: add the alias.) Exits have no aliases: their labels are the words, so give an exit every label a player might type.
108
+
109
+ ## Characters: orders and FOLLOW
110
+
111
+ A character can be told to do things: `robot, go east`, `floyd, follow me`. Its `orders` rules answer, keyed by the verb, and `obeys` lets it carry out GO, TAKE, DROP and GIVE by itself. [Orders](https://mrballistic.github.io/brass-lantern/reference/world-schema#orders) has the whole story; [Orders, numbers and a buggy](https://mrballistic.github.io/brass-lantern/guide/building-worlds/recipes#orders-numbers-and-a-buggy) is a worked example.
112
+
113
+ In an `orders.go` rule, **`direction:`** is the place the character was told to go, as the player typed it. The robot in that recipe won’t drive onto sand by itself:
114
+
115
+ ```ts
116
+ robot: {
117
+ name: 'robot',
118
+ description: 'A squat robot on treads, with one clamp for an arm.',
119
+ obeys: ['go', 'take'],
120
+ obeyReplies: { go: 'Whirr, click!', take: 'Click!' },
121
+ orders: {
122
+ go: [{ if: 'direction:dunes', say: ['The robot looks at its treads, then at the sand, and stays put.'] }],
123
+ },
124
+ },
125
+ ```
126
+
127
+ ```
128
+ > robot, go to the dunes
129
+ The robot looks at its treads, then at the sand, and stays put.
130
+ > robot, go east
131
+ Whirr, click!
132
+ ```
133
+
134
+ “Go to the dunes” matches `direction:dunes`, so the rule answers. “Go east” matches no rule, so the robot obeys. The word is compared as typed (lowercase, without “to the”), so `direction:east` works for compass directions too.
135
+
136
+ FOLLOW is a built-in verb. `follow floyd` doesn’t make Floyd anything; it tells the player how to bring him along. `floyd, follow me` is an order, so the character decides, in an `orders.follow` rule:
137
+
138
+ ```ts
139
+ floyd: {
140
+ name: 'Floyd',
141
+ article: '',
142
+ description: 'Floyd, a cheerful robot.',
143
+ orders: { follow: [{ if: 'target:player', then: 'floyd_tags_along' }] },
144
+ },
145
+ // in events:
146
+ floyd_tags_along: ['“Floyd go too!” he squeaks.', { follow: 'floyd' }],
147
+ ```
148
+
149
+ ```
150
+ > follow floyd
151
+ You’d rather Floyd came to you. Try FLOYD, FOLLOW ME.
152
+ > floyd, follow me
153
+ “Floyd go too!” he squeaks.
154
+ > north
155
+ …
156
+ Floyd follows you.
157
+ ```
158
+
159
+ **`article: ''`** is for proper names. The engine’s own lines about a character start with “The” (“The robot can’t go that way.”); `''` makes them “Floyd can’t go that way.” rather than “The Floyd …”.
160
+
161
+ An `instead.follow` rule on the character replaces the built-in reply to `follow floyd` (“Floyd is too quick for you.”).
162
+
163
+ ## Darkness
164
+
165
+ A room with `dark: true` needs a light the player brings in: an item with `light: true` that is on. In the dark the player can use only what they carry: acting on anything else says “It’s too dark to see.” (`darkness.tooDark` changes the line), and so does EXAMINE of a character who’s there. Talking to characters, giving them things, ordering them and fighting them still work.
166
+
167
+ ```
168
+ > examine troll
169
+ It’s too dark to see.
170
+ ```
171
+
172
+ A `flaming` item with no switch, such as a torch or a lit match, is already burning: LIGHT TORCH says “It’s already lit.” Being on fire doesn’t make it a light, though; give it `light: true` and switch it on with an effect if it should light rooms. [Darkness and death](https://mrballistic.github.io/brass-lantern/guide/building-worlds/recipes#darkness-and-death) is a worked example, and [Darkness](https://mrballistic.github.io/brass-lantern/reference/world-schema#darkness) lists the fields.
173
+
174
+ ## Saves and new versions of your world
175
+
176
+ Players’ saves hold the whole game: where every item is, flags, variables. When you ship a new version of a world with **new items**, a save made before them has no place for them. On load, the engine puts each item the save doesn’t mention where the world starts it, so an older save sees the new things where you placed them. An item the player used up (moved to `null`) stays gone. The browser terminal does this on every load; if you keep saves yourself, pass them through `migrateSave(world, raw)` from `@brass-lantern/engine` to get the same.
177
+
178
+ Say version 1 of the two-room game has nothing in the hall but the side table, and version 2 adds an umbrella. All you do is add it, as you would to a new world:
179
+
180
+ ```ts
181
+ // Version 1
182
+ hall: { name: 'Hall', /* … */ items: ['side_table'], /* … */ },
183
+
184
+ // Version 2: the umbrella is new
185
+ hall: { name: 'Hall', /* … */ items: ['side_table', 'umbrella'], /* … */ },
186
+ // and in items:
187
+ umbrella: { name: 'umbrella', description: 'A black umbrella, still damp.', portable: true, tags: [] },
188
+ ```
189
+
190
+ A player who saved in the hall under version 1 restores under version 2 and finds it there:
191
+
192
+ ```
193
+ > look
194
+ 📍 Hall
195
+ A dusty hall that smells of old books. The front door is south.
196
+ You can see: umbrella.
197
+ Sitting on the side table is:
198
+ A letter
199
+ Exits: south.
200
+ ```
201
+
202
+ In the save itself, `migrateSave` fills in the one place the old save was missing, and leaves everything else alone:
203
+
204
+ ```ts
205
+ // before: version 1’s save has no umbrella
206
+ locations: { doormat: 'porch', key: 'player', side_table: 'hall', letter: 'side_table', /* … */ }
207
+ // after migrateSave(version2, save): the umbrella starts where version 2 puts it
208
+ locations: { doormat: 'porch', key: 'player', side_table: 'hall', letter: 'side_table', /* … */ umbrella: 'hall' }
209
+ ```
210
+
211
+ That covers new items only. Change where an existing item starts and old saves keep it where it was; rename an item’s ID and old saves treat it as new, putting it back at its start even if the player had taken it. Prefer adding to renaming.
212
+
213
+ ## Style
214
+
215
+ - **Brass** (the default) is this engine’s own voice: “You can see: …”, “Taken: lamp.”, an exit line.
216
+ - **Infocom** (`style: 'infocom'`) follows Zork: “There is a lamp here.”, “Taken.”, no exit line, brief revisits. The native [Zork I](https://mrballistic.github.io/brass-lantern/guide/porting-zork) uses it.
217
+
218
+ Write player-facing text with curly quotes and apostrophes (“ ” ’), as the engine’s own replies do.