Room-based exploration with narrative evidence collection
"Every directory is a room. Every file is a clue. Navigation is investigation."
Turn exploration into a quest β or any simulation into a hybrid LLM/deterministic CLI.
Lineage: Colossal Cave (Crowther & Woods), Scott Adams Adventures, Zork (Infocom), MUD (Bartle), LambdaMOO (Curtis).
Inherits from: simulation/ β all simulation properties plus adventure-specific state.
[!TIP] This is a general pattern. Text adventure is the reference implementation, but the same architecture powers city sims, cloud management tools, board games β anything where deterministic transforms meet creative narration.
[!TIP] Perfect for codebase archaeology. "Find where the auth bug was introduced" β that's a quest!
An adventure creates a player with state, places them in a room, and the LLM dungeon masters them around.
# player.yml
name: Alice
location: entrance-hall
inventory:
refs: # Lightweight pointers (weight: 0)
- pub/bar/brass-lantern.yml
- street/acme-catalog.yml#portable-hole
objects: # Deep copies (has weight)
- { id: notebook, name: "Notebook", weight: 0.5 }
fungibles: # Stacks
- { proto: economy/gold.yml, count: 50 }
health: 100
notes: "Looking for the lost artifact"
Inventory Protocol: See skills/inventory/ for full TAKE/DROP/BOX/BEAM operations, pointer syntax, and structural editing.
The core loop:
User: "go north"
β DM: Updates player.location, describes the new room
User: "look around"
β DM: Reads room YAML, narrates contents atmospherically
User: "take the rusty key"
β DM: Moves key to player.inventory, narrates the action
The mapping:
This is Memory Palace with narrative framing and a player character.
Naturally supports multiple simultaneous participants:
# characters/
βββ alice.yml # Human player 1
βββ bob.yml # Human player 2
βββ merchant.yml # NPC (DM-controlled)
βββ guard-bot.yml # Autonomous bot (action queue)
βββ oracle.yml # LLM agent with own goals
Character types:
| Type | Controlled By | Example |
|---|---|---|
| Player Character | Human via chat | Alice exploring the dungeon |
| NPC | DM (LLM) responds when addressed | Merchant sells items |
| Bot | Action queue runs autonomously | Guard patrols on schedule |
| Agent | LLM with own goals & initiative | Oracle pursues prophecies |
All coexist in the same world:
# library/ROOM.yml
occupants:
- alice # Player exploring
- bob # Another player
- librarian # NPC who answers questions
- dust-sprite # Bot that cleans autonomously
Like The Sims and Populous, you have a selection β who you're controlling right now:
selection:
mode: single # or: group, swarm
current: alice # commands go to Alice
# Or control multiple at once:
selection:
mode: group
current: [alice, bob, charlie] # "go north" moves all three
# Or a whole swarm (Populous/Dungeon Keeper style):
selection:
mode: swarm
filter: { type: imp, location: mines }
Selection commands:
| Command | Effect |
|---|---|
SELECT alice |
Control Alice |
SELECT alice, bob |
Control both |
SELECT ALL imps |
Swarm control |
CYCLE |
Next character in rotation |
Commands apply to selection:
> SELECT alice, bob, charlie
> go north
Alice goes north.
Bob goes north.
Charlie goes north.
The coherence engine orchestrates all:
graph TD
START[π― Quest Objective] --> R1[Enter Room]
R1 --> LOOK[π Look Around]
LOOK --> EXAMINE[π Examine Objects]
EXAMINE --> COLLECT[π Collect Evidence]
COLLECT --> DECIDE{What next?}
DECIDE -->|New room| R1
DECIDE -->|Solved| END[π Quest Complete]
quest/
βββ ADVENTURE.yml # Quest state
βββ LOG.md # Narrative journal
βββ EVIDENCE/ # Collected clues
βββ MAP.yml # Explored territory
adventure:
quest: "Find the authentication bug"
status: in_progress
current_room: "src/auth/"
rooms_explored: 5
clues_found: 3
hypothesis: "Session cookie not being set"
confidence: 0.7
# Adventure Log
## Day 1: Entering the Auth Dungeon
I stepped into `src/auth/` β a maze of middleware.
**Clues found:**
- `session.ts` β handles cookie creation
- `middleware.ts` β checks auth state
**Suspicion:** The cookie is created but never sent...
| Command | Action |
|---|---|
GO [direction] |
Navigate |
LOOK |
Describe current room |
EXAMINE [object] |
Study a file |
TAKE [object] |
Add to inventory |
TALK TO [npc] |
Start conversation |
COLLECT [clue] |
Add to evidence |
DEDUCE |
Form/update hypothesis |
MAP |
Show visited rooms |
INVENTORY |
List held items |
DEBUG / DEBUG-ON |
Enable debug mode |
DEBUG-OFF |
Disable debug mode |
GLYPH / SUPERBRIEF / BRIEF / VERBOSE / INFODUMP |
Set the detail rung β see below |
Rule: every describable thing answers at any rung, and the reader picks the rung β not the author.
Adventure games shipped this in 1977. VERBOSE, BRIEF, and SUPERBRIEF are viewspecs:
per-reader, persistent across the session, set by the reader and applying to everything thereafter.
That is the same control Engelbart had in NLS a decade earlier, and the same thing OPML later stored
as expansionState. Three independent traditions concluded that a document needs a view knob, and
only the web shipped without one.
MOOLLM generalizes it past rooms. Any object β room, character, item, idea, design doc, commit, scene β must be renderable at every rung, because the rung is a property of the reading, not of the thing:
| Rung | Renders as | Classic |
|---|---|---|
GLYPH |
One mark. No text. A map symbol, a pie slice, a graph node | β |
SUPERBRIEF |
The name, every time, even on first encounter | Infocom |
BRIEF |
Full on first encounter, name thereafter | Infocom (default) |
VERBOSE |
Full description, every time | Infocom |
INFODUMP |
Everything attached: description, lore, provenance, backlinks, design notes, the YAML itself | β |
GLYPH and INFODUMP are the new ends. The glyph rung is the one that can be
apprehended in parallel β a map or contact sheet of glyphs is scanned pre-attentively, where a
page of descriptions is read serially. INFODUMP is what a curious reader or an agent wants: not
a better summary, all of it.
BRIEF is adaptive, and that is the sophisticated part. It is not a fixed detail level β it is
full once, then name only, which makes detail a function of what this reader has already seen.
A rung with memory. Nothing in the modern summary-ladder discussion has this, and it is strictly
better than a static setting: it spends words exactly where they are new. Implement it by reading
visit history, so BRIEF on a revisit is not a shorter description but a different one.
LOOK is the one-shot override. In the classic games LOOK redisplays the room in full
regardless of the current mode, without changing the setting. That is a peek: a temporary
excursion to a deeper rung that does not disturb your global preference β the same contract as a
link popup, and the same as reselection, where you
browse a consequence before committing to it. Keep LOOK doing exactly this, and let EXAMINE be
the object-scoped version.
SUPERBRIEF or GLYPH. GLANCE.yml is the glance rung; ROOM.yml carries the
name and the sign; README.md is the body. The three-file contract is the rung contract.designs/TAGSONOMY-COMPILER.md for the build-time
version, and ADVENTURE-COMPILER.md for compiling a world.VIEW-STATE-ANCESTORS.md.Toggle technical output with DEBUG-ON and DEBUG-OFF.
When debug is ON, logs include collapsible sections showing:
Example debug output:
<details open>
<summary>π <strong>Editing CHARACTER.yml to update player location from start/ to coatroom/</strong></summary>
```yaml
# State change (CHARACTER.yml is canonical)
player:
location: start/ β coatroom/ # Character owns their location
The character file owns location state. ADVENTURE.yml mirrors it for convenience.
Files affected:
When debug is OFF, output is clean narrative without technical sections.
Customize with natural language:
> DEBUG-FORMAT Show only file operations, skip YAML, use π§ emoji
The format field in ADVENTURE.yml accepts natural language instructions for how to format debug output.
Trading cards can be your adventure companions:
cards_in_play:
- card: "Index Owl π¦"
goal: "Search for cookie-related code"
- card: "Git Goblin π§"
goal: "Find when session handling changed"
Vision: Python CLI handles deterministic operations; LLM focuses on narrative. See README.md for full CLI vision and development plan.
| Layer | Python Does | LLM Does |
|---|---|---|
| State | Parse YAML, validate schemas | Generate content |
| Movement | Update coordinates | Narrate the journey |
| Scanning | Find pending work | Prioritize and process |
| Type | Description | Example |
|---|---|---|
| Clue | Information that might matter | "Different test runner versions" |
| Item | File worth remembering | CI config, setup.ts |
| Character | Code entity with personality | "jest.config.js β Strict about modules" |
| Map | Mental model of structure | Directory relationship diagram |
When entering any directory:
Adventures work for code exploration:
| Adventure | Investigation |
|---|---|
| Quest | Bug hunt |
| Room | Directory |
| Clue | Evidence |
| Companion | Tool card in play |
| Journal | session-log.md |
Best example: examples/adventure-4/ β The gold standard.
examples/adventure-4/pub/ β A complete social space:
pub/
βββ ROOM.yml # Themeable tavern (6 themes!)
βββ bartender.yml # NPC with 6 identity variants
βββ pie-table.yml # Octagonal debate table
βββ gong.yml # Gong of Gezelligheid
βββ bar/
β βββ bartender.yml # The omniscient bartender
β βββ budtender-marieke.yml
β βββ cat-cave/ # TARDIS-like cat sanctuary
β βββ ROOM.yml
β βββ 10 cats (Terpie, Stroopwafel, kittens...)
βββ arcade/ # Pacman, Pong, Pinball, Fruit Machine
βββ games/ # Chess, Darts, Cards
βββ stage/
β βββ palm-nook/ # Multi-room character space
β βββ study/ # Infinite typewriters, infinity desk
β βββ gym/ # Infinite climb
β βββ play/
β βββ rest/ # Hammock, silence cushion
βββ menus/ # Drinks, snacks, buds, games
Themeable NPCs (bartender.yml):
identity:
classic_adventure:
name: Grim
appearance: "Weathered human, salt-and-pepper beard..."
space_cantina:
name: Z-4RT
appearance: "Multi-armed service droid..."
cyberpunk_bar:
name: Nyx
appearance: "Chrome-implanted bartender..."
Themeable Rooms (pub/ROOM.yml):
theme:
current: classic_adventure
themes:
classic_adventure:
name: "The Gezelligheid Grotto"
bartender: "Grim, a weathered human"
menu: ["Ale (1 gold)", "Mystery meat pie (3 gold)"]
space_cantina:
name: "The Rusty Hyperdrive"
bartender: "Z-4RT, a droid with too many arms"
menu: ["Blue milk (1 credit)", "Bantha burger"]
Rich Activities:
activities:
PERFORM: { venue: stage, effects: [tips, drinks_thrown] }
DEBATE: { venue: pie_table, rules: roberts_rules }
RING-GONG: { protocols: [once: attention, twice: emergency, thrice: mercy] }
CELEBRATE: { effects: [free_round, +morale, everyone_toasts] }
Framing Protocol (for tribute performances):
framing:
mode: [performance, celebration, tribute]
tribute_protocol:
invocation: "Before they arrive, acknowledge we're summoning them"
performance: "Depicting them as we imagine their best selves"
acknowledgment: "After they depart, note this was a tribute"
graph LR
AP[βοΈ adventure] -->|IS-A| R[πͺ room]
AP -->|companions| TC[π΄ card]
AP -->|logs to| SL[π session-log]
AP -->|similar to| DB[π§ debugging]
MP[ποΈ memory-palace] -->|sibling of| AP
Adventure commands are options in Sutton's sense β semi-MDP macros, not iterated micro-transitions.
GO north jumps to an abstract room; the DM does not simulate every footstep.LOOK / EXAMINE use pickDescription LOD β detail on demand.TICK fires on meaningful moves, not every chat line.Anti-pattern: rolling out N implicit one-step predictions to answer "what happens if I keep going?"
Field case: Urban eBike Safari β voice options ("next Invader") then SET DESTINATION; Bosch turn-by-turn is micro layer only.
Full treatment: simulation/ONE-STEP-TRAP.md Β· examples/one-step-trap.yml
Field ride game: continuous AI guess/suggest; pie-menu network by biking; VoyStick + steering law β urban-safari-ride-game.yml Β· WWSFF room
CLI Uplift Plan, Browser Compilation, Scott Adams History, Owl Simulation See README.md for complete development roadmap and inspiration.