A new-school reimagining of Sierra's 1980s AGI interpreter in Rust + macroquad: - Dual-buffer (visual + priority) room model with hand-painted walkability - In-engine room editor: brush/line/rect/fill/pick/erase/oval/image/spawn/object tools - Sprite pipeline (PNG -> EGA quantize), multi-room worlds with edge exits - Objects with look/use/needs/keys/win conditions (no code) - Forgiving text parser + optional local-LLM lane (Ollama / OpenRouter) - Branching NPC dialogue trees, chiptune SFX, per-room ambient music - Content kit: 61 archetypes across fantasy/scifi/monsters/saucy/horror - Full docs: README, EDITOR_GUIDE, MANUAL, art manifesto + Gemini prompts Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
21 KiB
MRPGI — The Complete Manual
Monster Robot Party Game Interpreter — a new-school, Apple-Silicon-native
reimagining of Sierra's 1980s AGI adventure engine, written in Rust + macroquad.
You paint rooms, drop in objects, write puzzles, and walk through them — with a
text parser, optional local-AI understanding, branching dialogue, chiptune sound,
and ambient music. No external level files, no build step beyond cargo.
This manual is exhaustive. Jump around with the table of contents.
Table of contents
- What MRPGI is
- Install & requirements
- Running it
- The two modes
- Core concepts (read this)
- The editor — full reference
- Keyboard cheat sheet
- Building a world (rooms & exits)
- Objects & game logic
- The parser
- The AI parser lane
- Dialogue trees
- Sound & music
- The art / sprite pipeline
- The kit & theme packs
- Room file format (full schema)
- Project layout & architecture
- Troubleshooting
- Roadmap & known gaps
1. What MRPGI is
AGI was the engine behind King's Quest, Space Quest, and Police Quest. Its genius was a dual-buffer model: every room is drawn into a visual screen (what you see) and a hidden priority screen (depth + walkability). MRPGI keeps that soul and modernizes everything around it:
- In-engine room editor — paint walls, floors, and walkability by hand.
- Sprite pipeline — drop PNGs in a folder; they're auto-quantized to EGA.
- Multi-room worlds with edge exits.
- Objects with descriptions, locks, keys, and win conditions — no code.
- A forgiving parser + an optional local LLM that understands plain English.
- Branching dialogue, chiptune SFX, and ambient room music.
- A content kit: 61 click-to-place archetypes across fantasy, sci-fi, monsters, "saucy" nightlife, and classic horror.
Everything saves to small, human-readable JSON. The whole thing is one native
binary built with cargo.
2. Install & requirements
Required:
- Rust (stable, 1.85+). Install from https://rustup.rs or Homebrew.
- That's it for building — macroquad pulls its own dependencies and renders natively (Metal on macOS). No system libraries to install.
Optional (for the AI parser):
- Ollama running locally with a small instruct model (e.g. a Gemma 4B). Free, offline, private. Or an OpenRouter API key.
First build downloads the crate tree and takes a minute or two; subsequent builds are seconds.
3. Running it
cargo run # build + run (dev)
cargo run --release # optimized build (smoother), slower first compile
./run.sh # convenience launcher that points the AI lane at your model
Launch flags
| Flag | Effect |
|---|---|
| (none) | Loads rooms/room0.json and opens in the editor. |
--sample |
Writes a 3-room "escape the party" demo to rooms/ then runs it (existing rooms backed up to *.json.bak). |
--kit |
Generates the fantasy starter kit + placeholder sprites, and installs a 4-room fantasy world to rooms/ (existing → .bak). |
cargo run -- --kit # install + play the fantasy starter world
cargo run -- --sample # install + play the robot escape demo
AI environment variables
| Var | Default | Meaning |
|---|---|---|
MRPGI_AI |
ollama |
ollama · openrouter · off |
MRPGI_AI_MODEL |
gemma3:4b |
the model tag (set to YOUR Ollama tag) |
MRPGI_AI_URL |
http://localhost:11434/api/generate |
endpoint override |
OPENROUTER_API_KEY |
— | required when MRPGI_AI=openrouter |
MRPGI_AI_MODEL="your-gemma-tag" cargo run
MRPGI_AI=openrouter OPENROUTER_API_KEY=sk-... MRPGI_AI_MODEL=google/gemma-2-9b-it:free cargo run
MRPGI_AI=off cargo run # deterministic parser only
4. The two modes
MRPGI is always in one of two modes; Tab flips between them:
- EDIT — the painting/authoring mode. Left panel of tools, a canvas, a top strip for world/room controls. The engine boots here.
- PLAY — you become the robot. Walk with arrows or click, type commands, talk to NPCs. A bottom panel shows the transcript + command line.
Esc quits (in edit it first un-focuses a text field; in play it first ends a
conversation).
5. Core concepts (read this)
The dual buffer — LOOK vs MEANING
Every pixel of a room has two independent properties:
- LOOK — its colour (one of 16 EGA colours). What the player sees.
- MEANING — what it is to the engine: walkable floor, a wall, water, a trigger, or a depth band. Stored in a hidden "priority" buffer.
Colour does not imply meaning. A brown floor and a brown wall look identical but behave differently. When you paint, you set a colour and (optionally) a meaning tag; they're applied to the same region but stored separately. This is the single most important idea in MRPGI.
Meaning tags
| Tag | Effect on the priority buffer |
|---|---|
| floor | walkable, with automatic depth (lower on screen = drawn in front) |
| wall | blocked — the robot can't enter |
| water | reserved (special) |
| trigger | reserved (signal line for future logic) |
| none | paint colour only; leave walkability unchanged |
By default an empty room is entirely walkable — you paint walls where you don't want walking.
The picture
The play area is 160 × 168 logical pixels, displayed at a 2:1 pixel ratio (each pixel is twice as wide as tall — classic AGI). Colours are the 16-colour EGA palette (indices 0–15). A perfect circle drawn in art tools looks slightly wide in-game; that's intentional retro flavour.
EGA palette
0 black · 1 blue · 2 green · 3 cyan · 4 red · 5 magenta · 6 brown ·
7 light gray · 8 dark gray · 9 light blue · 10 light green ·
11 light cyan · 12 light red · 13 light magenta · 14 yellow · 15 white.
The ego
"Ego" is the AGI term for the player avatar (the little robot). Its position is
its feet (bottom-centre of its sprite), which is what determines its depth and
collision. In play it's blocked by wall pixels and walks off exit edges.
6. The editor — full reference
The editor screen: a tool panel on the left, the canvas on the right, and a world strip across the top of the canvas.
Tools (number keys, or click in the panel)
| Key | Tool | What it does |
|---|---|---|
1 |
brush | freehand paint with the current colour + meaning (size via , .) |
2 |
line | click to add points; right-click or Enter finishes the polyline |
3 |
rect | drag a filled rectangle |
4 |
fill | flood-fill a sealed region (the bucket) — fast |
5 |
pick | eyedropper; grab a colour off the canvas |
6 |
erase | rub back to blank + walkable |
7 |
image | stamp a sprite from sprites/ ([ ] to choose, R to reload) |
8 |
oval | drag a filled ellipse |
9 |
spawn | click to set where the robot appears in play (cyan circle) |
0 |
object | place a live object — see below |
Colour & meaning
- Palette (left panel): click a swatch to set the current LOOK colour.
- Meaning buttons: floor / wall / water / trigger / none — the MEANING applied by fills, rects, brush, etc.
View modes (L cycles)
- look — the plain picture.
- both — picture + walls tinted red, water blue, triggers green (so you can see what's walkable).
- ctrl — the raw meaning map (depth + control).
The object tool & kit browser (0)
With the object tool active the panel shows kit [ ]: <name>. Press [ ]
to flip through 60+ ready-made archetypes (orc, console, cocktail, coffin…). Click
an empty spot to stamp the selected archetype, fully written. Cycle to
(blank) to drop a bare object named after the current sprite. Click an existing
object to select it; the inspector then shows editable fields:
- name — what the parser calls it.
- look text — response to
look. - use text — response to
use/open. - needs item — an item required to
useit (empty = unlocked). - synonyms — extra nouns it answers to (space-separated).
- takeable / fixed toggle, wins toggle, delete.
Click a field, type, Enter jumps to the next field, Esc finishes.
The world strip (top)
- room N +
<>— move between rooms (also-=). Switching auto-saves. - exits
N E S W— click to cycle each edge's target room number. - ♪ — cycle the room's ambient music mood (off · calm · eerie · tense · jolly · spooky).
Saving
Ksaves the current room torooms/room<N>.json(an editable list of strokes- objects + exits + spawn + music).
Oreloads it.Cclears,Z/Backspaceundoes. The game auto-loadsrooms/room0.jsonon startup.
- objects + exits + spawn + music).
7. Keyboard cheat sheet
Global: Tab edit ⇄ play · Esc quit (or un-focus / end conversation) ·
F1 CRT scanlines · F2 mute sound + music.
Editor: 1–0 tools · L view · [ ] choose sprite / kit archetype ·
, . brush size · - = prev / next room · Z/Backspace undo · C clear ·
K save · O load · R reload sprites + kit.
Play: arrows / click to walk · type a command + Enter · 1–6 choose a
dialogue reply · Esc leave a conversation.
8. Building a world (rooms & exits)
A room is one screen. A world is rooms linked by their edges.
- Paint a room. Leave a walkable gap in a wall where you want a doorway.
- On the strip, set that edge's exit to a room number (e.g.
E: 1). - Press
>to go to that room (going past the last room makes a new blank one), paint it, and set a matching exit back. - In play, walk off the edge with an exit → you arrive in the target room, entering from the opposite side.
Rooms are files: rooms/room0.json, room1.json, … Room 0 is where the game
starts. The spawn tool sets each room's entry point (used if the edge you
arrive at is walled).
9. Objects & game logic
Objects are sprites you can interact with. Each carries:
- a name + synonyms (what the parser matches),
- look text (shown on
look), - use text (shown on
use/open), - needs — an item that must be in your inventory to
useit, - wins — using it successfully ends the game with a victory banner,
- takeable — whether
takeadds it to your inventory.
This is enough for the classic adventure loop with no code:
Put a
key(takeable) in one room and agatein another with needs = key, use text = "The gate grinds open…", wins = on. Now:take key→ travel →use gate→ win.
Taking an object removes it from the room (runtime only — it returns on a fresh play). Objects do not block movement (walls come from the painted meaning layer).
10. The parser
Type a sentence; it's lower-cased, filler words are dropped, the first word maps to a canonical verb, and the rest is the noun (matched against object names + synonyms, loosely).
Verbs (and their synonyms):
- look — look, l, examine, x, inspect, check, read, view, see, study
- take — take, get, grab, pick, snatch, steal, pocket, collect, nab
- use — use, open, unlock, activate, push, pull, turn, operate
- drop — drop, discard, leave, put
- talk — talk, speak, ask, chat, greet, say
- inventory — inventory, inv, i, items, bag
- help — help, ?, commands, verbs
Ignored filler: the, a, an, at, to, with, on, of, my, your, some, please, go, up.
look with no noun (or look around) lists what's in the room. Unknown verbs fall
through to the AI lane (if enabled) or print "I don't understand".
11. The AI parser lane
When the deterministic parser can't recognise your verb, MRPGI can hand the raw
sentence to a small LLM, which translates it into one allowed command — then
re-runs that through the normal logic. The AI bends the language; your game
still decides what's possible. It never invents items or outcomes, and it runs on
a background thread so the game never stalls. The chosen command is shown as
(≈ open crate).
Setup (local, recommended): run Ollama with an instruct model and point MRPGI
at it via MRPGI_AI_MODEL (see §3). Default provider is ollama. If Ollama isn't
running or the model is missing, the lane stays silent and you get the normal
deterministic behaviour — nothing breaks.
OpenRouter: set MRPGI_AI=openrouter, OPENROUTER_API_KEY, and a
MRPGI_AI_MODEL like google/gemma-2-9b-it:free.
The bottom bar shows ai: ollama/<model> (or off).
12. Dialogue trees
An object can carry a branching conversation. In play, talk <name> starts it:
the NPC speaks and you press a number key (1–6) to choose a reply. Choices jump
to other lines; some end the chat. Esc leaves. Walking pauses while you talk.
Dialogue is authored as a dialogue array on the object (a visual builder is
planned). Each entry is a node; node 0 is the entry. See the inn barkeep in
rooms/room3.json for a working example:
"dialogue": [
{ "says": "You're for the cave, aren't you?",
"choices": [
{ "text": "What's down there?", "goto": 1 },
{ "text": "Just passing through.", "goto": 3 }
] },
{ "says": "Orcs. Dark. A spellbook nobody should read aloud.",
"choices": [ { "text": "Thanks.", "goto": -1 } ] }
]
A choice's goto is the next node index, or -1 to end. A node with no
choices is a terminal line (shown, then the conversation ends).
13. Sound & music
All audio is synthesized at runtime (square waves → PCM, no files), in the spirit of AGI's PCjr beeper.
SFX (play mode): a blip when you submit a command, a ding on take, a chime on
a successful use/look, a buzz when nothing matches, a whoosh on room change, a
4-note fanfare on win.
Ambient music: each room can loop one of five moods (calm / eerie / tense / jolly / spooky), set with the ♪ strip button in the editor and saved per room. It plays in play mode and switches as you move between rooms.
F2 mutes music and SFX together. If a machine has no audio backend, everything
degrades to silence.
14. The art / sprite pipeline
Sprites live in sprites/ as PNGs. On load, any image is quantized to the EGA
palette (with transparency from its alpha), so it depth-sorts and walks-behind
like native art. Replace any sprite and press R to reload.
The sprite spec:
- PNG with transparency. Transparent pixels are see-through.
- Small, at game scale. A prop is ~12–28 px tall; draw 1:1.
- Filename = id. lowercase, no spaces (
orc.png). The object'sspritefield references this name. A missing sprite shows as a magenta placeholder. - Bottom-centre = feet. Stand the thing on the bottom edge of the canvas.
- EGA colours ideal but not required — load
palettes/mrpgi-ega.gplinto Aseprite/Inkscape, or let the engine snap your colours on load.
Aseprite: Palette → Load Palette → palettes/mrpgi-ega.gpl; draw small;
export PNG to sprites/; press R.
Inkscape: set a tiny px page; Export PNG (transparent) to sprites/.
AI (Gemini "Nano Banana" / Imagen): each theme's gemini_sprites.md has a
shared STYLE line + per-sprite prompts. Generate on a solid magenta background, key
it out to transparency, save into sprites/. (See also palettes/mrpgi-ega.gpl
and palettes/mrpgi-ega.hex.)
15. The kit & theme packs
cargo run -- --kit generates the fantasy starter kit and installs a playable
4-room world (forest → cave → dungeon → inn, with a key/spellbook/altar quest).
The reusable library lives in kit/:
kit/objects.json— 13 fantasy object archetypes.kit/locations/— prefab rooms (forest, cave, dungeon, inn).kit/ART_MANIFESTO.md— exact sprite specs to replace the placeholders.kit/themes/{scifi,monsters,saucy,horror}/— each withobjects.json(12 archetypes), a samplelocations/*.json, andgemini_sprites.md.
Use an object: the kit browser (object tool, [ ]) stamps any of these by
clicking. Or copy a { … } block from any objects.json into a room's objects
array. Use a location: cp kit/themes/horror/locations/crypt.json rooms/room5.json,
then wire its exits in the editor.
16. Room file format (full schema)
A room is a RoomDoc serialized as JSON:
{
"strokes": [ <Stroke>, ... ],
"spawn": [x, y],
"exits": [N, E, S, W], // each: a room number or null
"objects": [ <ObjDef>, ... ],
"music": 0 // 0=silence, 1..5 = mood
}
Stroke is an externally-tagged enum — exactly one key:
{ "Rect": { "x":0,"y":0,"w":160,"h":168,"color":2,"meaning":"Floor" } }
{ "Line": { "pts":[[x,y],...], "color":15 } }
{ "Brush": { "pts":[[x,y],...], "color":6,"meaning":"Wall","size":2 } }
{ "Ellipse": { "x":..,"y":..,"w":..,"h":..,"color":..,"meaning":"None" } }
{ "Fill": { "x":..,"y":..,"color":..,"meaning":"Floor" } }
{ "Image": { "name":"crate","x":..,"y":..,"meaning":"Wall" } }
meaning is one of "None", "Floor", "Wall", "Water", "Trigger".
color is an EGA index 0–15.
ObjDef:
{
"name": "gate", "sprite": "door", "x": 80, "y": 120,
"look": "A heavy gate.", "takeable": false,
"synonyms": "door portcullis", "use_text": "It grinds open!",
"needs": "key", "wins": true,
"dialogue": [ <DlgNode>, ... ]
}
DlgNode: { "says": "...", "choices": [ { "text": "...", "goto": 1 } ] } —
goto is a node index or -1 to end.
All fields except name/sprite/x/y default if omitted, so hand-written JSON
can be minimal. Strokes are replayed into the buffers on load, so rooms stay tiny
and re-editable.
17. Project layout & architecture
src/
main.rs window, game loop, modes, parser, dialogue runtime, compositing
palette.rs the 16-colour EGA palette (index → RGBA)
framebuffer.rs visual + priority buffers, the band(y) depth table
picture.rs drawing primitives (lines, rects, flood-fill, ellipse, stamps)
view.rs sprites as cels; ASCII-grid + image cel builders
sprite.rs the ego + props: movement, collision, walk-cycle
assets.rs PNG load + EGA quantize/dither; placeholder sprite generation
editor.rs the whole editor: tools, panel, RoomDoc, objects, kit, save/load
ai.rs the AI parser lane (Ollama / OpenRouter, on a worker thread)
sound.rs chiptune SFX + ambient music (square-wave WAV synthesis)
room.rs legacy demo-room helper
kit/ the archetype/theme library + art manifesto
sprites/ sprite PNGs (placeholders + your art)
rooms/ your world (room0.json …)
palettes/ the EGA palette for Aseprite/Inkscape (.gpl, .hex)
Rendering: rooms rasterize into a 160×168 visual buffer + a priority buffer.
Each frame, the ego and props are drawn back-to-front by depth; a sprite pixel is
only drawn where sprite_priority ≥ scenery_priority (the walk-behind trick). The
result is uploaded to a GPU texture and scaled crisp to the window. A fixed-step
"cycle" (20/s) drives movement, decoupled from frame rate.
18. Troubleshooting
- Objects are little magenta squares → that sprite PNG doesn't exist yet in
sprites/. Make it (see §14) or it's just a placeholder; the object still works. - A fill flooded the whole screen → your outline has a gap; the region wasn't
sealed.
Zto undo and close the gap. - Robot stuck after a room transition → the edge you arrived at is walled; leave a walkable gap there (it otherwise falls back to the room's spawn).
- AI does nothing → check the bottom bar
ai:readout; ensure Ollama is running andMRPGI_AI_MODELmatches a pulled model (ollama list). It only fires on unknown verbs. - No sound → some machines lack an audio backend; MRPGI stays silent rather
than crashing. Check
F2isn't muted. - Typing a letter flipped a mode → in play, letters go to the command line;
use
Tab/F1/F2/Esc(non-letters) for controls.
19. Roadmap & known gaps
- In-editor dialogue-tree builder (dialogue is JSON-authored for now).
- The kit-browser villager has no built-in dialogue yet (the live inn one does).
- More theme packs; title screen / world picker; save/load a playthrough.
- Water/trigger control lines are reserved but not yet wired to behaviour.
- True-colour ("HD") background layer and polygon fill regions.
MRPGI — built from "let's reimagine AGI" into a complete, AI-assisted, audible adventure-game maker. Have fun making weird little worlds. 🤖🎸