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>
13 KiB
MRPGI room editor — the dummy's guide
You draw a room, tag what's walkable, press a key, and walk around it. That's it.
Run it
cargo run
It opens in EDIT mode. Press Tab any time to flip between edit (draw) and
play (walk). Esc quits.
The one rule (read this twice)
Every spot on the screen has two properties, and you set them separately:
- LOOK = the colour (what you see). You pick it from the palette.
- MEANING = what it is to the game (walkable? a wall?). You pick it from the meaning tags.
So a brown floor and a brown wall look identical but behave differently — because colour is just looks; the tag is the meaning. When you fill or draw a region, it gets both: the current colour and the current meaning tag.
Meaning tags:
- floor — walkable (the robot can stand here). Default.
- wall — blocked (the robot can't enter).
- water / trigger — special (reserved for later).
- none — paint colour only, don't change walkability.
Make your first room (the classic 3-band road)
- Press
Tabuntil the panel says you're editing (you start here). - Click the fill tool (or press
4). - Click blue in the palette, click the wall tag, then click the top third → blue sky that blocks.
- Click green, keep wall, click the bottom third → green ground that blocks.
- Click brown, click the floor tag, click the middle band → a brown road that's walkable.
- Press
Tab→ you're now playing. Use the arrow keys — the robot can only walk the road. 🎉
Tip: tap L to cycle the view — both tints walls red so you can see
what's walkable vs blocked. look shows the plain picture; ctrl shows the meaning map.
Tools
| Tool | Key | What it does |
|---|---|---|
| brush | 1 |
freehand paint (colour + meaning) |
| line | 2 |
click points; right-click or Enter to finish the line |
| rect | 3 |
drag a filled box |
| fill | 4 |
flood-fill a sealed area (the bucket) |
| pick | 5 |
eyedropper — grab a colour off the canvas |
| erase | 6 |
rub back to blank + walkable |
| image | 7 |
stamp a sprite from sprites/ ([ ] choose, R reload) |
| oval | 8 |
drag a filled ellipse |
| spawn | 9 |
click to set where the robot appears in play |
| object | 0 |
stamp a kit archetype ([ ] picks from 60+) or a blank object; click again to select |
Brush/eraser size: , smaller, . bigger. The fill bucket (4) is your fast filler — outline a shape with lines, then click inside it.
Sealed areas: fill only stays inside lines that fully enclose a space. If a
fill suddenly floods the whole screen, your outline has a gap — undo and close it.
The status line warns you (LEAK?).
Keys
Tabedit ⇄ play ·Escquit1–0tools ·Lcycle view[]choose sprite ·,.brush size ·Rreload sprites-=previous / next room (the top strip has<>too)ZorBackspaceundo ·Cclear room ·Ksave ·Oload- In play: arrows / click to walk · type a command + Enter (try
talk <npc>) · in a conversation press 1–6 to choose, Esc to leave ·F1CRT ·F2sound
Saving
K saves to rooms/room.json (the editable list of your strokes, not flat pixels —
so it stays re-openable and tiny). The game auto-loads that file on startup, and
O reloads it. The cyan circle on the canvas is where the robot spawns in play.
Bring your own pixel art (Aseprite / Inkscape)
The dream loop: draw a thing → drop the PNG in sprites/ → press R → place it with the image tool (7).
The spec (what a sprite PNG should be)
- Format: PNG with transparency (alpha). Transparent pixels stay see-through in game; a sprite with no transparency acts like a solid tile.
- Size = game size. The room is 160×168 "fat" pixels — draw small, at 1:1. Rough guide: a person ≈ 12–20 px tall, a crate ≈ 24 px, a door ≈ 30×50. A 500 px sprite will fill the whole screen.
- Colours: ideally the MRPGI EGA 16 — load
palettes/mrpgi-ega.gplinto your tool. Off-palette art still works; it's auto-snapped to the nearest EGA colour on load. - Name = id. Lowercase, no spaces:
crate.png,barrel.png,becky.png. The filename (minus.png) is what shows in the sprite tray. - Where: the
sprites/folder. (Startercrate/barrel/signare already there — replace them or add your own.)
Aseprite (pixel art)
- Load the palette: Palette panel → ☰ menu → Load Palette →
palettes/mrpgi-ega.gpl. Now you're painting in the exact game colours. (Bonus: Sprite → Color Mode → Indexed = pixel-perfect, zero quantization.) - New sprite, small (e.g. 24×24), transparent background.
- Draw. Leave everything that isn't the object transparent.
- Export: File → Export Sprite Sheet or just Save As a
.pngintosprites/. - In MRPGI: press
R, tool7,[]to your sprite, click to place.
Inkscape (vector → pixel)
- Draw your art. File → Document Properties → set a small px canvas (e.g. 24×24).
- Export: File → Export → Page tab, set width/height in px, transparent background → export PNG into
sprites/. - Same place step in MRPGI (
R,7, place).
How a placed sprite behaves
The image tool stamps the sprite into the room picture and applies your current meaning tag underneath it. So:
- want a solid crate? pick the wall tag before you stamp → you can't walk through it.
- want a walk-over rug? pick floor (or none to leave walkability alone).
It's baked into the room and saved in room.json. (A live object — a movable entity with a name + verbs for items/NPCs — is the next slice; just ask.)
One quirk: fat pixels
Game pixels are twice as wide as tall (classic AGI). A perfect circle in Aseprite shows up slightly wide in game. Ignore it early — it's part of the retro look.
Build a world (multiple rooms)
One room is a screen. A world is rooms linked by their edges.
- The top strip shows
room Nwith<>to move between rooms (also keys-/=). Going>past the last room makes a fresh blank one. Switching rooms auto-saves the one you're leaving. - Each room saves to its own file:
rooms/room0.json,rooms/room1.json, … - Exits (top strip): the
N E S Wbuttons. Click one to cycle its target room number (N:-→N:0→N:1… → off). SoE:1means "walk off the right edge → arrive in room 1". - Set the entrance with the spawn tool (
9): click where the robot should appear. The cyan circle shows it.
Make two linked rooms
- In room 0, leave a walkable gap at the right edge (don't wall it). Set exit
E:1. - Press
>to go to room 1, draw it, leave a walkable gap on its left edge, set exitW:0. - Press
<back to room 0, pressTabto play, walk off the right edge → you're in room 1. Walk back left → room 0. You built a world. 🌍
Tip: if the robot seems stuck after a transition, the edge it arrived at was walled — leave a floor gap there (it otherwise falls back to that room's spawn point).
Objects you can talk to (the parser)
This is where it becomes a game. The object tool (0) places a sprite as a real thing — and in play you can type at it.
Place an object
- Tool
0(object). Press[]to flip through the kit — 60+ ready-made archetypes (orc, console, cocktail, coffin, sword, spellbook…), shown in the panel askit [ ]: <name>. Click the canvas to stamp the selected one — fully written (look/use/needs/wins/synonyms all filled in). Cycle to(blank)to instead drop a bare object named after the current sprite. - Click an object to select it. The inspector has editable fields — name, look text, use text, needs item, synonyms — plus takeable / wins toggles and delete.
- To write its description: click the look text field, type your line (a caret shows), press Enter to jump to the next field. Click the canvas or press Esc to finish typing.
Ksaves it with the room.
Talk to it (in play)
Press Tab to play. There's a > command line at the bottom — type and hit Enter:
look barrel→ its descriptiontake key→ into your inventory (if it's takeable)inventory(ori) → what you're carryinglook around→ everything in the roomhelp→ the verb list
Arrows still walk while you type. The parser is forgiving now: it ignores filler words ("the", "a", "at") and knows verb synonyms — look = examine = x = check = inspect; take = get = grab = pick. And the synonyms field lets an object answer to other nouns: put box wood cargo on a crate and examine the box just works.
Try it
- Edit: tool
0, drop acrateand abarrel, pressK. Tabto play → typelook around, thenlook crate, thentake barrel, theninventory. The game answers you. 🤖💬
Make a puzzle (locks, keys, winning)
Objects can now do things — no code, just fields. Each object's inspector has:
- use text — what
use <it>/open <it>prints. - needs item — an item it requires. Empty = no lock. If set,
useonly works when that item is in your inventory; otherwise it says "It's locked. You need: key." - wins toggle — using it successfully wins the game (a YOU WIN banner).
A complete 30-second game
- Drop a sprite, select it, set name =
key, takeable = on. (That's your key.) - Drop another, set name =
gate, use text = "The gate groans open. You're free!", needs item =key, wins = yes. Kto save,Tabto play.- Type
use gate→ "It's locked. You need: key." take key, thenuse gate→ your line prints + ★ YOU WIN ★ 🏆
use also accepts open, unlock, push, pull, turn. Keys, locks, and a win condition — a real puzzle, authored entirely with fields.
Natural language (the local AI lane)
When the parser doesn't recognise your verb, it quietly asks a local LLM to translate your sentence into one real command — then runs THAT through the normal game logic. The AI bends the language; your game still decides what's possible. It can't invent items or outcomes. It runs on a background thread (the game never freezes) and shows what it decided, e.g. (≈ open crate).
It's already set up on your Mac
Ollama is running with your Gemma models. Because your models have custom names, point the engine at one when you launch:
./run.sh
# (which is just: MRPGI_AI_MODEL="hf.co/OBLITERATUS/gemma-4-E4B-it-OBLITERATED:latest" cargo run )
Then in play, with a gate / key / crate around, try:
pry the lid off the box→ (≈ open crate)yank the gate open→ (≈ use gate)grab that shiny thing→ (≈ take key)
The bottom bar shows ai: ollama/<model> so you know it's wired.
Settings (env vars)
| Var | Default | Meaning |
|---|---|---|
MRPGI_AI |
ollama |
ollama · openrouter · off |
MRPGI_AI_MODEL |
gemma3:4b |
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 |
OpenRouter instead of local:
MRPGI_AI=openrouter OPENROUTER_API_KEY=sk-... MRPGI_AI_MODEL=google/gemma-2-9b-it:free cargo run
If Ollama isn't running or the model's missing, the AI lane just stays quiet and you get the normal "I don't understand" — nothing breaks.
Talk & tunes (NPCs + ambient music)
Talk to NPCs
An object can carry a branching dialogue tree. In play, talk <name> (or speak / ask / chat / greet) starts a conversation: the NPC speaks, and you pick a reply by pressing its number key (1–6). Choices branch to other lines; some end the chat. Esc leaves early. Walking pauses while you talk.
Try it: in the fantasy world, head south from the forest to the inn and type talk barkeep — there are a few branches to explore.
Authoring is JSON for now: a dialogue array on the object (see the inn villager in rooms/room3.json). Each node has says + choices; each choice has text + goto (a node index, or -1 to end). A visual tree-builder is a future step.
Ambient music
Each room can loop a mood. In the editor, the top strip has a ♪ button — click it to cycle: off · calm · eerie · tense · jolly · spooky. It saves with the room (K) and plays in play mode, switching as you move between rooms. F2 mutes music + SFX together.