The handover plan (docs/HANDOVER.md), P0-P6, in one pass. One insight powers it: CLI/HTTP/MCP/Python is one feature, not four — a headless core driven by Commands in, Events out, with every surface a thin adapter. - mrpgi-core: the whole sim as a library with zero macroquad (the workspace boundary is the guardrail). GameState::apply/tick, fixed 1/20s cycles, deterministic replays, headless RGBA/PNG rendering. - mrpgi-headless: JSONL stdio REPL, --script replay (AI lane off = byte-identical), --render-room PNG, --serve HTTP (state/command/ events cursor/frame.png), --mcp JSON-RPC stdio server whose render_frame returns a real PNG so a model sees its own moves. - Games are folders: game.json manifest (all fields default — legacy dirs still load), data-driven verb table feeding both the parser and the AI whitelist, flags with requires_flag/sets_flag gating on objects and dialogue choices, live UpsertRoom lore ingestion with validation. - mrpgi (GUI): now one consumer of the bus; editor commits through the same commands MCP uses; audio became core events with per-game sfx/ music/ file overrides beating the chiptune synth. - Tests: parser goldens + a full win-path playthrough asserting byte-identical transcripts across runs. Deleted: the old fused src/ (root is now a virtual workspace), the dead demo Scene/Assets path. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
6.6 KiB
MRPGI — Monster Robot Party Game Interpreter
A new-school, Apple-Silicon-native reimagining of Sierra's 1980s AGI engine — the tech behind King's Quest, Space Quest, and Police Quest — rebuilt in Rust + macroquad so you can make cool, fun adventure games.
It keeps the soul of AGI (vector rooms, the invisible priority/depth screen, loops-and-cels sprites, a verb-noun parser, flags & vars, the ego) and bridges it forward (Retina-crisp rendering, hand-editable text resources, a real embedded scripting language, and an optional AI parser).
Run it
cargo run -p mrpgi # the GUI (paint + play) — or ./run.sh
cargo run -p mrpgi --release # smooth, optimized build
A window opens in the editor. Paint a room (or launch with --sample for the
3-room demo game), then press Tab to walk it — arrows or click to move,
type verbs to play. F1 toggles CRT scanlines, F2 sound, Esc quits.
Drive it from anywhere (headless)
The engine is a headless core behind a typed command/event bus; the GUI is just one consumer. The same bus is exposed as JSONL stdio, HTTP, and MCP:
cargo run -p mrpgi-core --bin mrpgi-headless -- --sample --game games/demo
echo '{"cmd":"parse","text":"look"}' | cargo run -p mrpgi-core --bin mrpgi-headless -- --game games/demo
mrpgi-headless --serve 127.0.0.1:7777 --game games/demo # HTTP + /frame.png
claude mcp add mrpgi -- mrpgi-headless --mcp --game games/demo # Claude plays & authors
A game is a folder of JSON + PNGs — an LLM expansion is just JSON that deserializes. See docs/CONTROL.md for the full guide (commands, events, HTTP routes, MCP tools, authoring JSON reference).
How it works (the AGI model, faithfully)
Every room is drawn into two buffers at once:
| Buffer | What it holds | Why |
|---|---|---|
| visual | palette indices (0–15, EGA) | what you see |
| priority | depth bands (4–15) + control lines (0–3) | depth sorting & collision |
Lower on screen ⇒ higher priority ⇒ drawn nearer the camera. The ego is drawn a
pixel at a time, only where its priority ≥ the scenery's — that's the whole
"walk behind the tree" trick, with zero per-object Z bookkeeping. Control lines
(priority 0) are walls the ego can't cross.
Source map
Two crates: mrpgi-core (the headless engine — zero windowing deps, a
compile error is the guardrail) and mrpgi (the macroquad GUI).
| File | Role |
|---|---|
mrpgi-core/src/state.rs |
the spine: GameState, Command/Event bus, fixed-step tick |
mrpgi-core/src/world.rs |
rooms/objects/dialogue data model (all serde), World, game folders |
mrpgi-core/src/parser.rs |
data-driven verb table, verb-noun parser, dialogue state |
mrpgi-core/src/render.rs |
headless compositing → RGBA / PNG (the priority-buffer trick) |
mrpgi-core/src/framebuffer.rs |
the visual + priority buffers and the band(y) depth table |
mrpgi-core/src/picture.rs |
vector room painting (rects / lines / fills / stamps) |
mrpgi-core/src/view.rs |
sprites as loops + cels, authored from tiny ASCII grids |
mrpgi-core/src/sprite.rs |
the ego: movement, click-to-walk, collision, walk animation |
mrpgi-core/src/assets.rs |
PNG → EGA quantize + dither; sprite folders; kit art |
mrpgi-core/src/audio.rs |
chiptune synth + audio cues (played by front-ends, or files) |
mrpgi-core/src/ai.rs |
AI parser lane (Ollama / OpenRouter / off) on a worker thread |
mrpgi-core/src/kit.rs |
sample game + fantasy starter kit, built from the real types |
mrpgi-core/src/bin/mrpgi-headless/ |
JSONL stdio, --script replay, --render-room, HTTP, MCP |
mrpgi/src/main.rs |
window, play loop over the bus, HUD |
mrpgi/src/editor.rs |
the in-engine paint tool (edits go through the same bus) |
mrpgi/src/sound.rs |
audio out: game-folder files override the built-in synth |
Asset pipeline (the new-school bit)
Images don't sit on top of the game — they're folded into it. Any PNG is quantized to the EGA palette (optional Bayer dithering for smooth retro gradients) and becomes indexed pixels, so from that moment it depth-sorts, gets occluded, and walks-behind exactly like hand-authored art. Three ways in:
- Tile an image across a region (textures — e.g. the floor)
- Pour an image into a shape via
stamp_cel_fitwith a fit mode (stretch / tile / center) — the shape also stamps depth & collision. This is the "draw a shape, load an image into it" idea, realized. - Objects — a PNG becomes a
Propthat sorts with the ego
Drop your own art into assets/, then press R in-game to hot-reload it.
Roadmap
- v0.1 — rendering core: vector room, priority depth, walkable ego, click-to-walk, CRT toggle
- v0.2 — asset pipeline: PNG → EGA quantize + dither, tile / pour-into-shape / object insertion, hot-reload (
R) - v0.3 — room editor: in-engine paint tool with edit/play modes, dual-layer LOOK+MEANING painting, flood-fill, tools (brush/line/rect/fill/pick/erase), undo, and JSON room save/load — see EDITOR_GUIDE.md
- Priority-mask backgrounds: a full-room art PNG + a companion mask PNG whose colors encode depth bands & control lines (the pro art workflow)
- True-color "HD" layer mode: skip quantization for painted backdrops; the priority buffer stays indexed
- Polygon shapes for
stamp_cel_fit(not just rectangles) - Directional views (4 loops + horizontal mirroring) for the robot
- Text parser: data-driven verb table with synonyms, verb-noun matching, on-screen input line
- Room transitions, inventory, flags (with
requires_flag/sets_flagpuzzle gating) - AI parser adapter: when the classic parse misses, an LLM maps free text onto an already-allowed command (deterministic core stays authoritative; works offline without it)
- Loadable game folders (
game.json+ JSON rooms + PNGs) so games ship without recompiling the engine - Headless core + control surfaces: JSONL stdio, script replay, HTTP, MCP, headless PNG rendering — see docs/CONTROL.md
- Rhai room logic: rooms scripted in an AGI-flavored embedded language (
if said(...) { print(...) }), hot-reloadable - 3-voice + noise chiptune synth (per-game audio files already override the square-wave synth)
- Strudel sink: browser sidecar over the HTTP surface, driven by the same audio events
- Vars & conditions on dialogue/objects beyond boolean flags; cross-room NPC state
- Pathfinding for click-to-walk (today: greedy steering)