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>
231 lines
13 KiB
Markdown
231 lines
13 KiB
Markdown
# 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
|
||
|
||
```sh
|
||
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)
|
||
|
||
1. Press **`Tab`** until the panel says you're editing (you start here).
|
||
2. Click the **fill** tool (or press `4`).
|
||
3. Click **blue** in the palette, click the **wall** tag, then click the **top third** → blue sky that blocks.
|
||
4. Click **green**, keep **wall**, click the **bottom third** → green ground that blocks.
|
||
5. Click **brown**, click the **floor** tag, click the **middle band** → a brown road that's walkable.
|
||
6. 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
|
||
|
||
- `Tab` edit ⇄ play · `Esc` quit
|
||
- `1`–`0` tools · `L` cycle view
|
||
- `[` `]` choose sprite · `,` `.` brush size · `R` reload sprites
|
||
- `-` `=` previous / next room (the top strip has `<` `>` too)
|
||
- `Z` or `Backspace` undo · `C` clear room · `K` save · `O` load
|
||
- In play: **arrows / click** to walk · **type a command + Enter** (try `talk <npc>`) · in a conversation press **1–6** to choose, **Esc** to leave · `F1` CRT · `F2` sound
|
||
|
||
## 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.gpl` into 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. (Starter `crate` / `barrel` / `sign` are already there — replace them or add your own.)
|
||
|
||
## Aseprite (pixel art)
|
||
|
||
1. **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.)
|
||
2. New sprite, small (e.g. 24×24), transparent background.
|
||
3. Draw. Leave everything that isn't the object transparent.
|
||
4. **Export:** File → *Export Sprite Sheet* or just *Save As* a `.png` into `sprites/`.
|
||
5. In MRPGI: press **`R`**, tool **`7`**, **`[` `]`** to your sprite, **click** to place.
|
||
|
||
## Inkscape (vector → pixel)
|
||
|
||
1. Draw your art. *File → Document Properties* → set a small px canvas (e.g. 24×24).
|
||
2. **Export:** *File → Export* → Page tab, set width/height in **px**, transparent background → export PNG into `sprites/`.
|
||
3. 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 N` with `<` `>` 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 W` buttons. Click one to cycle its target room number (`N:-` → `N:0` → `N:1` … → off). So `E:1` means "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
|
||
|
||
1. In **room 0**, leave a walkable gap at the **right** edge (don't wall it). Set exit **`E:1`**.
|
||
2. Press `>` to go to **room 1**, draw it, leave a walkable gap on its **left** edge, set exit **`W:0`**.
|
||
3. Press `<` back to room 0, press **`Tab`** to 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
|
||
1. Tool **`0`** (object). Press **`[` `]`** to flip through the **kit** — 60+ ready-made archetypes (orc, console, cocktail, coffin, sword, spellbook…), shown in the panel as `kit [ ]: <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.
|
||
2. 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**.
|
||
3. 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.
|
||
4. `K` saves 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 description
|
||
- `take key` → into your inventory (if it's takeable)
|
||
- `inventory` (or `i`) → what you're carrying
|
||
- `look around` → everything in the room
|
||
- `help` → 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
|
||
1. Edit: tool `0`, drop a `crate` and a `barrel`, press `K`.
|
||
2. `Tab` to play → type `look around`, then `look crate`, then `take barrel`, then `inventory`. 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, `use` only 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
|
||
1. Drop a sprite, select it, set **name** = `key`, **takeable** = on. (That's your key.)
|
||
2. Drop another, set **name** = `gate`, **use text** = *"The gate groans open. You're free!"*, **needs item** = `key`, **wins** = yes.
|
||
3. `K` to save, `Tab` to play.
|
||
4. Type `use gate` → *"It's locked. You need: key."*
|
||
5. `take key`, then `use 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:
|
||
|
||
```sh
|
||
./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:**
|
||
```sh
|
||
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.
|