ego_view() decided whether a game overrode the default party robot by
stat'ing sprites/ego.png on disk. A wasm build has no disk — the game is
baked into the binary with include_dir — so the check always failed and
every web player was a party robot regardless of what the game shipped.
Read the already-loaded sprite table instead, which both loaders populate
by file stem, so native and web now agree and native does one less read.
The transcript was drawn one draw_text per line with no wrapping, so any
line wider than the window ran off the right edge and was simply lost.
Room descriptions here are whole paragraphs, so this ate the end of most
of them. Greedy word-wrap on measured width, and the visible row count is
derived from the panel geometry rather than hardcoded at 5. Panel grows
150 -> 186 so a wrapped paragraph fits without pushing its own opening
line off the top.
Also: Game > Manual opened monsterrobot.games/engine/manual.html, which
has not served the manual since that domain moved hosts — it silently
returns the arcade index (200, so nothing looked wrong). Point native at
the repo's own web/manual.html, which always matches the build.
Verified: all 16 policesquadquest win paths still replay to max score,
and both fixes confirmed rendering in the native --shot and on the live
deploy.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Point --game at a directory that is not itself a game but contains game
folders, and the GUI boots into a title screen: the collection's name in
gold (optional collection.json {"name": ...}, else the folder name), a
shelf of games sorted by folder with display names from each game.json,
a dimmed live preview of the selected game's start room baked by the
headless renderer, arrow-key selection, Enter to play, and Esc from
inside a game returning to the shelf instead of the desktop.
Shelf names strip a shared "Series — " prefix when every game has one —
the collection title already names the series, and it also sidesteps the
em-dash glyph missing from macroquad's default font.
Core grows world::is_game_dir / scan_collection / collection_name with a
unit test; the GUI grows Mode::Title. --shot pointed at a collection
captures the title screen (that is how the screenshots in this commit's
review were taken). Sample/kit scaffolding is skipped for collection
dirs so a shelf never gets a stray sprites/ folder written into it.
Closes the "title screen / world picker" roadmap item in MANUAL §20.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two general-purpose additions, both needed by an external game folder and
useful to any other:
- World::ego_view() — a game folder with sprites/ego.png uses it as the
player avatar instead of the hardcoded party robot (optional ego1.png
gives the walk bob a second cel). Refreshed on LoadGame and ReloadSprites.
- GameManifest.death_text — the death banner was hardcoded to morp2's
"Morp is not cruel; Morp is accurate", which leaked that game's flavour
into every other one. Now a manifest field; the default keeps the old
wording minus the morp2 reference, so existing games are unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- MRPGI_WEB_GAME env (default games/lost-fuse via .cargo/config.toml)
selects the game include_dir! embeds; web/build.sh <game-dir> stages
just game.json+rooms+sprites (a repo's raw art was ballooning the wasm
to 41MB; now 1.9MB) and ships the game's web/index.html and music/.
- sound.rs: file overrides load through macroquad's loader (fs on native,
HTTP fetch on web), and AudioOut now loads inside a coroutine behind
LazyAudio — browsers gate audio decoding behind a user gesture, and
blocking boot on it was a black screen. First frame draws immediately;
music starts when ready, remembering the requested mood/mute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
New authoring surface (all serde-defaulted; old games load unchanged):
- RoomDoc: name + enter_text (announced on entry), exit_flags/exit_blocked
(per-direction flag gates with authored refusal lines, latched per attempt)
- ObjDef: points (once per room+name), consumes, kills, visible_flag,
hidden_by_flag; DlgChoice: points (latched on sets_flag flip), kills
(goto node's says = death text)
- GameManifest: max_score enables scoring; GUI shows the classic
'Score: X of Y' in the menu bar; score_changed + died on the bus
- Death restarts the day, keeping the cause at the top of the fresh transcript
Reviewed by an adversarial multi-agent pass; confirmed findings fixed:
corner push-back no longer slingshots through a perpendicular open exit
(and checks the landing pixel), blocked messages latch per attempt,
kill choices pay no points/flags, new_game re-zeroes the score on the bus,
AI-lane prompts respect object visibility. Deliberate behavior change:
drop/consumes now emit inventory_changed (was a silent client desync).
Built for MORPQUEST (games/… ssh://…/monster/mrpquest.git), which uses all
of it: 250-point budget, four gated exits, two deaths, vanishing Beckies.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Scheduled daily at 4am on the ultra. launchd can't exec scripts inside
~/Documents (TCC), so the job runs a copy under Application Support;
header documents the sync/manage/remove commands.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The vault's user data (argon2 hashes + saved worlds) only lives on the
VPS and isn't in git. backup.sh pulls /opt/mrpgi-vault/data down as a
timestamped tarball, keeps the 30 newest, and documents the restore
path in its header.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- web/manual.html — 17 chapters: playing, the editor deeply, the
authoring cookbook (full JSON reference + the five load-bearing
puzzle patterns), dialogue trees, flags, the art pipeline with
sprite-generation prompts, sound, driving the engine with code
(real JSONL transcripts), AIs + MCP, prompt patterns with actual
outputs (the roof room is the real shipped expansion), the web
build & cloud saves, engine internals, extension recipes,
troubleshooting, and full command/event/palette reference.
- Game menu gains "Manual": window.open on the web (via a JS plugin
import), the default browser natively.
- README tab links the Book; repo README points at it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mrpgi-vault (standalone crate): tiny signup/login/world-save service.
Argon2 password hashes, random session cookies, JSON-file storage,
naive per-IP rate limit, 4MB body cap. tiny_http, no framework, no db.
Runs as a container on the dealgod network; nginx proxies
/engine/api/ to it.
- core: WorldBundle — a whole world (manifest + every room) as one JSON
document, with World::to_bundle/apply_bundle.
- web bridge: mrpgi_request_save / mrpgi_alloc / mrpgi_world_in exports
+ an mrpgi_world_saved JS plugin import; the page's SAVE button pulls
the live world out of the running engine, LOAD swaps it back in.
SAVE_REQUESTED is an AtomicBool on purpose — wasm's thread-less
Mutex<bool> is a plain static to LLVM and release builds const-folded
the read (the only writer is JS-visible, not IR-visible).
- web UI: SIGN IN tab → create account (own username + password) /
sign in / Save world / Load world / sign out. Session restores on
revisit.
Verified live end-to-end through Cloudflare: signup → save → reload →
anonymous rejected; throwaway account removed after.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- The classic dropdown menus across the top of the play screen: Game
(New Game / Edit Mode / CRT / Sound / About) and Action (Look Around /
Inventory / Help). Pure bus consumers — menu items inject the same
Commands every other surface uses. Sliding along the bar switches
menus, clicks outside close, clicks never leak into click-to-walk.
- web: README tab pinned top-right with the short guide — controls,
editor shortcuts (undo is just Z, no Ctrl), the two-layer paint model,
and why the lake is scenery for now.
- --shot out.png: render a few Play frames and save the real backbuffer
to PNG — ground-truth GPU screenshots for debugging and CI. (Used to
prove the menu colors were right when a preview browser's auto-dark
filter was inverting light neutrals on canvas.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live at https://monsterrobot.games/engine/ (dealgod VPS, dealgod-nginx
/engine alias over /opt/dealgod/images/engine/).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- mrpgi-core: ureq/tiny_http/threads are native-only deps; the AI lane
compiles to an always-off stub on wasm32 (deterministic parser stays).
- World::from_memory + assets::cel_from_bytes — boot a game with no
filesystem.
- mrpgi GUI on wasm: embeds games/lost-fuse via include_dir, boots
straight into Play (Tab still opens the editor; saving needs a disk,
so web is play + paint-only for now).
- web/: index.html shell + macroquad JS loader + build.sh producing a
self-contained static web/dist (1.1 MB total, game included).
- .cargo/config.toml: --allow-undefined for miniquad's JS imports.
Verified in-browser: renders, typed commands reach the parser,
click-to-walk works, zero console errors.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- MANUAL.md brought up to the workspace era: game folders + manifest,
flags & gated puzzles, hidden dialogue choices, audio file
overrides, a Programmatic control section (CLI/HTTP/MCP/Python),
and the new two-crate architecture map.
- games/lost-fuse: three rooms that demonstrate every mechanic in one
chain — item, needs-lock, object sets_flag, a dialogue choice hidden
behind requires_flag, dialogue sets_flag, and a flag-locked wins
finale. Verified winnable headlessly, gate included.
- games/lost-fuse/expansion/roof.jsonl: a live lore drop that upserts
a fourth room into a running game and rewires an exit — the "drop
JSON lore in" workflow, runnable as a one-liner.
- games/README.md: the guided tour — mechanic-to-JSON map, the live
expansion demo, and how to prompt an LLM (or point Claude's MCP at
the engine) to write valid lore.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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>
Captures the 2026-07-05 design session: making MRPGI fully
programmatically controllable (MCP + CLI/stdio + HTTP/WebSocket) via a
headless mrpgi-core engine behind a typed command/event bus, with the
macroquad GUI as one optional front-end.
Includes: current-state analysis, the core refactor (GameState with
apply/tick), games-as-folders + JSON lore ingestion, an "always want
options" audio design (SynthSink / FileSink mp3-ogg-wav / StrudelSink,
all toggleable), a P0-P6 roadmap, quick wins, risks, and a source map.
Adds docs/architecture.svg (self-contained) plus an inline Mermaid
diagram in the handover.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>