MRPGI/EDITOR_GUIDE.md
type-two d0caf983a0 Initial commit: MRPGI — Apple-Silicon AGI adventure engine
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>
2026-06-17 17:26:55 +10:00

231 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 **16** 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 ≈ 1220 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.