The title screen / world picker now works in the browser, not just natively. `./web/build.sh <folder-of-game-folders>` stages every game inside it plus collection.json and bakes the lot into one wasm; the engine boots into the shelf exactly as it does on the desktop. Sixteen cases of Police Squad Quest come to 2.1 MB total. - embedded_world() becomes embedded_world_at(prefix), so one bundle can hold many games; embedded_games() / embedded_collection_name() are the browser twins of world::scan_collection / collection_name. - The picker is now keyed by String on both platforms and resolved through one load_by_key() — a filesystem path natively, an embedded subdirectory in the browser — so Mode::Title has a single implementation. - Esc is a no-op on the web when there is nowhere to back out to. Breaking the main loop in a browser just leaves a dead black canvas; found by pressing it. - ascii_fold() on every game-text draw. macroquad's built-in font has no em dash, curly quote, star or dagger, so room names like "— Squad Room —" and every death card were drawing tofu boxes in both builds. Folding at the draw layer fixes the whole engine without making game authors type worse prose. - web/build.sh uses the rustup toolchain explicitly: cargo/rustc on PATH may be Homebrew's, which ships no wasm32 std even though `rustup target list` shows the target installed, and the resulting "can't find crate for core" is a thoroughly misleading error. Verified in a real browser: shelf renders, arrows move the selection and swap the live room preview, Enter loads and plays the case, Esc returns to the shelf with the selection preserved. Native --shot and all 13 core tests still pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> |
||
|---|---|---|
| .cargo | ||
| assets | ||
| docs | ||
| games | ||
| kit | ||
| mrpgi | ||
| mrpgi-core | ||
| mrpgi-vault | ||
| palettes | ||
| rooms | ||
| sprites | ||
| web | ||
| .gitignore | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CLAUDE.md | ||
| EDITOR_GUIDE.md | ||
| MANUAL.md | ||
| README.md | ||
| run.sh | ||
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).
📖 The deep read: THE MRPGI BOOK — the combined user/dev manual with the authoring cookbook, AI prompt patterns with real outputs, and engine internals. Live at monsterrobot.games/engine/manual.html and under Game → Manual in the engine itself.
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)