An Irish Living World Text Adventure, set in 1820 rural Ireland; powered by the custom Limerick engine. 1820 was chosen as it in the middle of the period after the Acts of Union 1800 that brought Ireland into the United Kingdom of Great Britian and Ireland, and prior to the Great Famine.
Rundale is resetting around a native iPhone text adventure: SwiftUI presents a status header, transcript, and composer; the Limerick Rust runtime and authoritative saves live on device; remote inference goes through Limerick Endpoints. The product specifications define the six gated milestones: first a fixture-only interaction prototype, then embedded gameplay in a tiny world. These requirements are not a claim that the mobile client is complete.
The capabilities, screenshots, and commands below describe the existing game, desktop/web clients, and developer tools retained as implementation reference. They do not define feature-parity requirements for the mobile reset.
The player arrives as a newcomer to Kilteevan Village, about two miles south-east of Roscommon town in County Roscommon. The village and surrounding area is populated with numerous non-player characters. NPCs are driven by LLM inference. A cognitive level-of-detail (LOD) system simulates NPCs at varying fidelity based on proximity to the player. The geography is based on real early 19th century Ireland. The characters and establishments are fictional.
Four binaries built from this workspace, each with a single job:
flowchart LR
subgraph Engine["Limerick engine (limerick-core composes 14 leaf crates)"]
Core[("game loop · world · NPCs · inference · save store")]
end
Repl["**limerick-engine --headless**<br/>stdin/stdout REPL<br/>(also `--script` batch)"]
Tauri["**limerick-tauri**<br/>desktop app<br/>(Svelte 5 UI + Tauri IPC)"]
Server["**limerick-server --port PORT**<br/>Axum HTTP/WS server<br/>(library + binary)"]
Browser["Browser<br/>(serves the same Svelte UI)"]
Client["**limerick-client**<br/>thin HTTP shell<br/>(single-shot · script · REPL · JSON)"]
MCP["**limerick-mcp**<br/>MCP bridge for AI agents"]
Repl --> Core
Tauri --> Core
Server --> Core
Browser -. HTTP/WS .-> Server
Client -. POST /api/command .-> Server
MCP -. HTTP .-> Server
| Binary | Mode | Has engine in-process? | When to use |
|---|---|---|---|
limerick-tauri | just run | yes | Default desktop experience — full GUI. |
limerick-engine | --headless (just run-headless), --script FILE | yes | Single-process terminal play; deterministic --script runs drive the test harness. |
limerick-server | --port PORT (just web) | yes (one engine per cookie session) | Multi-user web server; serves the same Svelte UI; the target for limerick-client, MCP, and browser sessions. |
limerick-client | single-shot / --script / --json / REPL (cd limerick && just run-client) | no — thin shell | Drive a running limerick-server over HTTP. Use from scripts, CI, or as a lightweight terminal alternative to the browser. |
limerick-mcp | MCP server (bash limerick/scripts/limerick-mcp-backend.sh start) | no — bridge | Expose mcp__limerick__* tools to AI agents (Claude Code, etc.). Also bridges over HTTP to a running backend. |
Shared rule: mode parity. Every gameplay feature behaves identically across Tauri, headless, and web. Shared orchestration lives in limerick-core; entry-point crates contain only thin wiring (see docs/agent/architecture.md).
/speed./listen hears its weather-, shelter-, season-, and time-aware soundscape; /omen notices a present detail that might be taken as a sign, while refusing to predict the future; and /folklore recalls the location's exact authored tradition without inventing one when no account comes readily to mind. Natural discussion of omens, folklore, or listening to the world can add a brief atmospheric cue while the underlying conversational turn continues. The default-on place-listening flag controls all three.A four-tier simulation that scales hundreds of NPCs at varying fidelity based on proximity to the player:
/v1/messages API, not the OpenAI-compatibility shim), Google Gemini (native Interactions API), OpenRouter, Groq, xAI Grok, Mistral, DeepSeek, Together AI, Custom (any OpenAI-compatible base URL), and a built-in offline Simulator that needs no model download. Google Gemini 3.7 Flash at Low thinking is the default cloud model across Dialogue, Simulation, Intent, and Reaction, with role-specific output caps, implicit-cache usage telemetry, and Google's Standard service tier. (Additional providers are available via mod-loaded configurations — Cohere, GitHub Models, Qwen, Zhipu, OpenCode Zen, and others.) Local profiles are available for macOS (vllm-mlx) and Linux/Windows (vLLM/Ollama), but none currently passes Rundale's production dialogue promotion gate; first-run setup labels them experimental and recommends BYOK cloud for player dialogue./provider.dialogue, /model.intent, /key.simulation).gemini-3.7-flash with the qualified Low-thinking, 4,096-token production profile. A 12-interaction live soak averaged $0.00460 per NPC dialogue interaction (0.46¢; observed range $0.00372–$0.00552) at Google's promotional Standard rates through December 31, 2026. At a human pace of roughly 15 billable NPC exchanges per hour—one every four minutes—that is about $0.069 per gameplay hour; a 10–20-exchange pace is approximately $0.046–$0.092/hour, excluding background-model traffic. The retained multi-family judgments, individual API calls, and latency evidence are published in the local qualification dashboard rather than inferred from general-purpose benchmarks.{mood, action, internal_thought, irish_words}; partial JSON is recovered on truncation.@mention targeting to address a specific NPC in a crowded room.*nods thoughtfully* italicized inline.player-task-progression flag.branches, snapshots, journal_events); readers never block writers, so autosave can fire mid-conversation without hitching./fork <name> creates a non-destructive branch from the current state; /load switches; /branches lists./save and graceful-shutdown autosave on /quit.M./theme — default cream/parchment, Solarized Light, Solarized Dark — driven by CSS custom properties and persisted in localStorage so reloads don't flash the wrong palette.dmooney/rundale by default), embedding the screenshot inline. Per-record buttons attach the exact inference call / event / conversation as context. Every report also carries a "black box" diagnostic payload — the raw LLM prompt/response history, the canonical get_engine_state snapshot, and the last raw user intent — so local-inference drift is reproducible. Also available to auto-QA agents via the limerick_file_bug MCP tool. Gated by the default-on bug-report flag; configured via LIMERICK_BUG_REPORT_TOKEN / LIMERICK_BUG_REPORT_REPO, with LIMERICK_BUG_REPORT_DRY_RUN=1 writing the report to disk instead of filing.limerick_engine_state MCP tool exposes the canonical, deterministic engine state (active scene, clock, weather, player, NPCs, gossip grapevine) so an agent can assert the UI resolved each state transition. The limerick/scripts/limerick-mcp-audit.sh lifecycle script wraps a strict Init → Execute → Validate (UI vs get_engine_state) → Teardown (file a bug on mismatch, kill the backend cleanly) loop. Gated by the default-on engine-state flag.? help, Tab through semantic controls, Enter activate/send, and Esc close a dismissible surface or stop the demo./editor for authoring NPCs, locations, schedules, and mod data without touching JSON directly; see the Limerick Designer section below.crates/limerick-server serves the same Svelte UI over HTTP + WebSocket, one isolated session per limerick_sid cookie.<user-data>/saves/<session_id>/ and survives restarts. The user-data root is platform-native (~/Library/Application Support/Rundale on macOS, $XDG_DATA_HOME/rundale on Linux, %APPDATA%\Rundale on Windows) and named after the active mod's save_root. Override with LIMERICK_SAVES_DIR (saves), LIMERICK_TILE_CACHE_DIR (tile cache), or LIMERICK_USER_DATA_DIR (root)./metrics for auth failures, session counts, and inference call stats.Dockerfile in deploy/.limerick-engine — single-process binary with two modes: --headless (stdin/stdout REPL), --script FILE (deterministic batch driver), no flag (Tauri-launch). HTTP serving is no longer muxed in — limerick-server is now a runnable binary in its own right.--script <file> mode for deterministic JSON-in/JSON-out execution — the backbone of the test harness.limerick-client)limerick binary that talks to a running limerick-server over HTTP — no engine in-process, no game state owned locally.limerick "<cmd>" single-shot, limerick --script <file> for batch fixtures, limerick no-arg REPL, limerick --json "<cmd>" for raw CommandResponse JSON suitable for piping into jq / automation.limerick_sid cookie is saved between runs so subsequent invocations resume the same save branch.mod.toml manifest declares world, NPCs, prompts, anachronisms, festivals, encounters, transport, pronunciations, UI overrides, and loading-screen text.world.json — locations with id, description templates, lat/lon, indoor/public flags, edge connections, mythological significance, and a geo_kind (real / manual / fictional).npcs.json — full NPC schema with personality, six-axis intelligence, home/workplace, mood, and per-season hourly schedules.A GUI editor embedded in the SvelteKit UI at the /editor route, accessible from both the Tauri desktop app and the web server (LIMERICK_ENABLE_EDITOR=1). Follows the mode-parity rule — every editor command is implemented once in limerick-core and wired to both backends.
mods/, switch between them without restarting.{time}, {weather}, {npcs_present}), lat/lon, indoor/public flags, and connection editing with enforced bidirectional edges.WorldGraph::validate() plus orphan NPC homes/workplaces, broken relationship targets, and schedule location refs; click any issue to jump to the field..db save files, branches, and snapshots; view deserialized world state (clock, weather, NPCs, gossip network, conversation log); export a snapshot as a fixture JSON.git diff stays clean even after a no-op round-trip.limerick-geo-tool — Overpass-API CLI that pulls real Irish features into world.json by named area or bounding box, with cached responses, dry-run preview, hand-curated merge mode, and a realign-coords utility for snapping to historical map coordinates.limerick-npc-tool — SQLite-backed NPC builder: bulk-generate parish or county populations with seedable randomness and 1820s demographic weights, query/filter by parish/occupation/tier, edit moods, promote tiers, batch-elaborate backstories with an LLM, validate referential integrity, and export/import JSON. Also splits the monolithic mods/rundale/npcs.json catalogue into per-NPC source files (split-catalog) and re-joins them into a byte-identical canonical file (join-catalog), with a standalone validate-catalog integrity pass.limerick-harness — headless game quality-control harness: runs automated multi-turn playtests where an LLM plays the player and an LLM judges the finished transcript, against limerick-server over HTTP. Each run captures canonical engine state and a rendered telemetry "state-frame" per turn—not a player-visible UI screenshot; evaluates deterministic hard-fail gates (crash / parser-reject / timeout / empty-turn-burn); scores ~7 quality axes (0–100) when gates pass; records findings; and persists everything to SQLite plus on-disk artifacts. The Tauri bridge does not expose the /api/command endpoint this client uses, so desktop/UI quality is covered separately by the live MCP quality harness and Playwright lanes. Run knobs (engine models per category, feature flags, player persona, judge rubric pinned by sha256) are content-addressed for exact A/B comparison and correlated with git history. The player/judge seam runs either deterministic scripted actors (CI, no key) or limerick-inference-backed LLMs (Anthropic / OpenAI-compat / local vllm-mlx). Drive with cargo run -p limerick-harness -- run --config <cfg> --turns N against a running server. For a fully headless real-model game to drive, boot the web server with limerick-server --headless-models (or LIMERICK_HEADLESS_MODELS=1): it detect-reuses (or spawns) the bundled vllm-mlx Qwen two-slot loadout and binds the four inference categories to it, so POST /api/command produces genuine NPC dialogue. The harness applies per-run BYOK model overrides (engine_models.<category>) through runtime slash commands over /api/command, resolving provider keys from the harness environment at apply-time (never persisted into the content-addressed run config). For unattended CI/cron runs, limerick-harness run --player api --judge api is driven solely by env API keys—no Claude Code session, MCP, or subagent queue—and --player/--judge select each actor's driver independently.limerick-scenario — versioned YAML regression runner for agents and CI. Every step drives the shipping limerick_core::game_loop, mocks only inference, and evaluates explicit assertions over emitted IPC events and post-step state. Run all scenarios with just scenario-test or print one JSON report with just scenario-run <file>.test_*.txt fixtures in testing/fixtures/ retain compatibility coverage through structured ScriptResult output. One-off demonstrations are separated under testing/proofs/ and are not counted as regression tests merely because they execute without crashing.Vec<ScriptResult> JSONs in testing/evals/baselines/, with structural rubrics that gate against empty look descriptions, frozen clocks, and anachronistic vocabulary.crates/limerick-core/tests/architecture_fitness.rs mechanically enforces leaf-crate purity (no tauri/axum/tower in shared logic), CLI-vs-leaf duplication bans, and orphaned-module detection. Each failure prints a self-correcting hint.justfile with ~50 recipes grouping build, test, harness, lint, screenshots, deps, geo/NPC tooling, Ollama control, and local CI via act.just witness-scan rejects AI completion stubs (the usual todo! and ellipsis-comment patterns) in changed files.just check-doc-paths ensures every backtick-cited file path in docs/ actually exists.just screenshots).docs/index.md is the master hub — phase status, design overview, ADR index, plans, research, and agent guides.docs/agent/ — slim, indexed reference for AI coding agents (build, architecture, style, gotchas, harness, skills, git workflow), linked from CLAUDE.md and AGENTS.md.Rundale ships with its own reproducible LLM benchmark that scores models as the engine's NPC brain — in-character dialogue, reaction, world simulation, intent, and Gaeilge (Irish-language) fluency — then prices each candidate against real gameplay token volume. The v2 promptfoo suite is the benchmark of record; the v1 harness is archived.
promptfoo/ — the v2 benchmark of record. The v1 harness is archived under rundale-bench/.promptfoo/leaderboard/leaderboard.md + leaderboard.jsonl (append-only history), committed once the first funded run is recorded.Rundale/Limerick is an experiment in building a world too detailed and too improvisational to author by hand. The premise is that AI can simulate a parish of hundreds of NPCs (or more) at varying fidelity, generate their dialogue and reactions on the fly, and remain coherent over long play sessions. I wanted to build something using AI that would be impossible any other way, at least for a solo dev.
To that end, the project is developed entirely by AI coding agents — mostly Claude Code, with Codex and Gemini on specific tasks. Quality control is an evolving combination of agents reviewing each other's work and extensive automated checks — the architecture-fitness tests, gameplay harness, eval rubrics, and snapshot baselines described above are designed to keep AI-written code honest. Human play-testing is the final gate.
Static game content for the Ireland in 1820 setting in mods/rundale/ — NPC personalities, schedules, relationships; location descriptions, lore, pronunciations — is also AI-generated, but human-reviewed before it lands.
Character dialogue, mood, and behaviour are generated in real time by whichever LLM provider you've configured. Every NPC line, gossip rumour, and Tier 2/3 simulation tick comes from a live model call at play time; nothing is pre-baked. Each playthrough is genuinely different, and the dialogue's quality depends on the model you point the engine at.
The workspace ships with a justfile; run just for the full set of recipes.
Requirements: Rust (edition 2024), Node.js (v20+), just (cargo install just or your package manager's equivalent), and an LLM endpoint configured in limerick.toml or .env. See .env.example for environment variables. There is no packaged release yet.
# One-time: install system deps, Rust, Node, and frontend packages
just setup
The default experience is a desktop app.
just run # launches cargo tauri dev
just run-headless # stdin/stdout REPL, engine in-process
just web # Axum web server on :3001 (Svelte UI in browser)
cd limerick && just run-client # thin HTTP REPL against just-web
Single-shot / scripted / JSON modes for limerick-client:
cargo run -p limerick-client -- "look" # one command, formatted output
cargo run -p limerick-client -- --script testing/proofs/play_X.txt # batch fixture
cargo run -p limerick-client -- --json "look" | jq .outcome # raw CommandResponse JSON
See the Ways to run Limerick diagram for how these binaries fit together.
For a shippable .app that ends users can double-click — no Python or
vllm-mlx install required — build the inference bundle first, then
the app:
just build-vllm-mlx-bundle # ~5 min, ~360 MB compressed, Apple Silicon only
cd limerick && cargo tauri build --target aarch64-apple-darwin
The first command materialises a relocatable Python runtime with
vllm-mlx pip-installed straight into its site-packages at
limerick/dist/vllm-mlx/python-runtime/ (using python-build-standalone's
install_only tarball — no venv, since absolute paths in pyvenv.cfg
would break when the bundle moves into Rundale.app/Contents/Resources/).
cargo tauri build then includes that tree under
Rundale.app/Contents/Resources/vllm-mlx/python-runtime/. On first
launch the app detects the bundle and offers the Qwen2.5 local profile
as an experimental option on capable Macs, with its qualification status
shown beside the BYOK recommendation. Choosing local downloads the weights
with a live progress bar.
CI driver: .github/workflows/build-vllm-mlx-bundle.yml (manual
trigger, uploads the bundle as an artifact). For dev iteration on
cargo tauri dev, you can skip the bundle build — the runtime falls
through to a PATH-installed vllm-mlx (i.e. uv tool install vllm-mlx).
One engine, three thin entry points, fourteen backend-agnostic leaf crates. The full crate-by-crate map lives in docs/agent/architecture.md.
flowchart TB
subgraph clients["Frontends & clients"]
UI["Svelte 5 UI<br/>limerick/apps/ui<br/>(one transport.ts for both backends)"]
CLI["Limerick CLI client<br/>limerick-client"]
MCP["limerick-mcp<br/>MCP bridge for AI agents"]
end
subgraph entry["Runtime entry points (thin adapters, mode parity)"]
TAURI["limerick-tauri<br/>Tauri 2 desktop"]
SERVER["limerick-server<br/>Axum HTTP + WS<br/>(sessions, auth, idempotency)"]
ENGINE["limerick-engine<br/>headless REPL / --script / Tauri launch"]
end
CORE["limerick-core — composition + orchestration<br/>ipc/ • game_loop/ • game_session<br/>event_bus • prompts<br/>(re-exports limerick-mod as game_mod,<br/>limerick-editor as editor,<br/>limerick-chronicle as character_log/location_log/chat_transcript,<br/>limerick-diagnostics as debug_snapshot)"]
subgraph leaf["Shared leaf crates (backend-agnostic, enforced)"]
WORLD["limerick-world<br/>graph, movement, weather, geo"]
NPC["limerick-npc<br/>cognitive LOD tiers 1–4, mood,<br/>memory, ticks, gossip<br/>(tier 4 = CPU rules, no LLM)"]
INPUT["limerick-input<br/>parsing, intent (local + LLM)"]
INFER["limerick-inference<br/>queue, priority lanes, worker, validation"]
PROVIDERS["limerick-providers<br/>provider HTTP clients, simulator/mock,<br/>AnyClient dispatch, rate limits"]
SETUP["limerick-setup<br/>GPU detect, model select,<br/>Ollama/vllm bootstrap"]
PERSIST["limerick-persistence<br/>SQLite WAL, journal, snapshots, branches"]
CONFIG["limerick-config<br/>TOML + env + flags"]
PALETTE["limerick-palette<br/>day/night palette"]
MOD["limerick-mod<br/>content-mod loader<br/>(manifest, discovery, world bridge)"]
EDITOR["limerick-editor<br/>Designer backend<br/>(mod I/O, validation, persistence, save inspect)"]
CHRONICLE["limerick-chronicle<br/>on-disk chronicle writers<br/>(character/location markdown logs, chat transcript)"]
DIAG["limerick-diagnostics<br/>debug-snapshot builders +<br/>bug-report orchestration"]
TYPES["limerick-types<br/>ids, time, events, errors (zero internal deps)"]
end
subgraph external["Content & external systems"]
MODS[("mods/rundale<br/>world.json, npcs.json, prompts…")]
DB[("SQLite saves<br/>per-user data dir")]
LLM["LLM providers<br/>Ollama / OpenAI-compat / Anthropic / simulator"]
end
UI -- "Tauri IPC invoke/listen" --> TAURI
UI -- "fetch + WebSocket" --> SERVER
CLI -- "POST /api/command" --> SERVER
MCP -- "HTTP :3030" --> SERVER
TAURI -- "handle_command + EventEmitter" --> CORE
SERVER --> CORE
ENGINE --> CORE
CORE --> WORLD & NPC & INPUT & INFER & PERSIST & CONFIG & PALETTE & MOD & EDITOR & CHRONICLE & DIAG & TYPES
INPUT -. "intent LLM" .-> INFER
NPC -. "T1 dialogue • T2 group sim + gossip • T3 batch sim" .-> INFER
NPC -.-> WORLD
PERSIST -.-> NPC
MOD -.-> WORLD
EDITOR -. "mod I/O + validation" .-> MOD
NPC -. "all leaves depend on types" .-> TYPES
MOD -- "mod.toml manifest + validation" --> MODS
PERSIST --> DB
INFER -- "dispatch via AnyClient" --> PROVIDERS
PROVIDERS --> LLM
classDef clientNode fill:#d7e7f7,stroke:#4a7aab,color:#1f2328
classDef entryNode fill:#fae3bd,stroke:#c08a2e,color:#1f2328
classDef coreNode fill:#e3d3f4,stroke:#8a5fb8,color:#1f2328
classDef leafNode fill:#cdeccf,stroke:#4f9457,color:#1f2328
classDef extNode fill:#ffffff,stroke:#777777,color:#1f2328
class UI,CLI,MCP clientNode
class TAURI,SERVER,ENGINE entryNode
class CORE coreNode
class WORLD,NPC,INPUT,INFER,PROVIDERS,SETUP,PERSIST,CONFIG,PALETTE,MOD,EDITOR,CHRONICLE,DIAG,TYPES leafNode
class MODS,DB,LLM extNode
style clients fill:#eef4fb,stroke:#9db8d4,color:#1f2328
style entry fill:#fdf3e3,stroke:#d8b873,color:#1f2328
style leaf fill:#e9f6ea,stroke:#9ccca0,color:#1f2328
style external fill:#f6f6f6,stroke:#bbbbbb,color:#1f2328
limerick/
crates/ 24 workspace members (runtime, scenario/harness tools, and leaf logic crates)
apps/ui/ Svelte 5 + TypeScript frontend
testing/fixtures/ scripted gameplay fixtures
scripts/ Maintenance and quality gate scripts
mods/rundale/ Rundale game content (world, NPCs, prompts, lore)
deploy/ Dockerfile
docs/ design, ADRs, plans, research, agent guides
justfile Top-level proxies for common tasks
The game icon was generated with ChatGPT (OpenAI image generation) from a hand-written prompt and is shipped as-is.
| Start here | What you'll find |
|---|---|
| docs/index.md | Master hub — phase status, links to everything |
| docs/repository-layout.md | Top-level directory tree and crate index |
| docs/troubleshooting.md | Bug reporting + inference-log artefact guide |
| docs/design/overview.md | Architecture, tech stack, module tree, LLM providers |
| docs/graphics-v2/README.md | Visual-client research: notebook UI, graphics pipelines, assets, and evidence |
| docs/requirements/roadmap.md | Per-item status tracking across all phases |
| docs/research/README.md | Research documents covering life in 1820's Ireland |
| docs/adr/README.md | Architecture decision records and rationale |
| AGENTS.md | Agent guide — index into docs/agent/ for build, style, and gotchas |
Rundale on the Limerick engine is © 2026 Dave Mooney and is licensed under the
GNU General Public License v3.0 (GPL-3.0-only). Source code is
free to use, modify, and redistribute under the terms of that licence.
"Rundale" and "Limerick" are unregistered trademarks of Dave Mooney. The
GPL covers source reuse but not the project names or logos: forks must
rename. (A formal trademark policy lives at TRADEMARK.md once published.)
Rundale is built on a stack of excellent open-source projects, including
Rust, Tokio,
Axum, Tauri,
Svelte / SvelteKit,
MapLibre GL JS, SQLite,
and Phosphor Icons. Full attribution with
licence texts is in THIRD_PARTY_NOTICES.md; run
just notices to regenerate the exhaustive transitive list.
Map data © OpenStreetMap contributors, licensed under the Open Database Licence 1.0. Historic 6″ Ordnance Survey Ireland tiles (1829–1842) reproduced with the permission of the National Library of Scotland, licensed under CC-BY. UI icons use Phosphor Icons under MIT. Map labels use Open Sans under the SIL Open Font License 1.1; its generated MapLibre glyph ranges and licence are bundled for offline web and desktop rendering.
1,159 commits
22 commits
Rust
47.4%
HTML
34.8%
TypeScript
5.9%
Python
4.9%
JavaScript
3.2%
Svelte
2.0%
Shell
1.2%
An Irish Living World Text Adventure, set in 1820 rural Ireland; powered by the custom Limerick engine. 1820 was chosen as it in the middle of the period after the Acts of Union 1800 that brought Ireland into the United Kingdom of Great Britian and Ireland, and prior to the Great Famine.
Rundale is resetting around a native iPhone text adventure: SwiftUI presents a status header, transcript, and composer; the Limerick Rust runtime and authoritative saves live on device; remote inference goes through Limerick Endpoints. The product specifications define the six gated milestones: first a fixture-only interaction prototype, then embedded gameplay in a tiny world. These requirements are not a claim that the mobile client is complete.
The capabilities, screenshots, and commands below describe the existing game, desktop/web clients, and developer tools retained as implementation reference. They do not define feature-parity requirements for the mobile reset.
The player arrives as a newcomer to Kilteevan Village, about two miles south-east of Roscommon town in County Roscommon. The village and surrounding area is populated with numerous non-player characters. NPCs are driven by LLM inference. A cognitive level-of-detail (LOD) system simulates NPCs at varying fidelity based on proximity to the player. The geography is based on real early 19th century Ireland. The characters and establishments are fictional.
Four binaries built from this workspace, each with a single job:
flowchart LR
subgraph Engine["Limerick engine (limerick-core composes 14 leaf crates)"]
Core[("game loop · world · NPCs · inference · save store")]
end
Repl["**limerick-engine --headless**<br/>stdin/stdout REPL<br/>(also `--script` batch)"]
Tauri["**limerick-tauri**<br/>desktop app<br/>(Svelte 5 UI + Tauri IPC)"]
Server["**limerick-server --port PORT**<br/>Axum HTTP/WS server<br/>(library + binary)"]
Browser["Browser<br/>(serves the same Svelte UI)"]
Client["**limerick-client**<br/>thin HTTP shell<br/>(single-shot · script · REPL · JSON)"]
MCP["**limerick-mcp**<br/>MCP bridge for AI agents"]
Repl --> Core
Tauri --> Core
Server --> Core
Browser -. HTTP/WS .-> Server
Client -. POST /api/command .-> Server
MCP -. HTTP .-> Server
| Binary | Mode | Has engine in-process? | When to use |
|---|---|---|---|
limerick-tauri | just run | yes | Default desktop experience — full GUI. |
limerick-engine | --headless (just run-headless), --script FILE | yes | Single-process terminal play; deterministic --script runs drive the test harness. |
limerick-server | --port PORT (just web) | yes (one engine per cookie session) | Multi-user web server; serves the same Svelte UI; the target for limerick-client, MCP, and browser sessions. |
limerick-client | single-shot / --script / --json / REPL (cd limerick && just run-client) | no — thin shell | Drive a running limerick-server over HTTP. Use from scripts, CI, or as a lightweight terminal alternative to the browser. |
limerick-mcp | MCP server (bash limerick/scripts/limerick-mcp-backend.sh start) | no — bridge | Expose mcp__limerick__* tools to AI agents (Claude Code, etc.). Also bridges over HTTP to a running backend. |
Shared rule: mode parity. Every gameplay feature behaves identically across Tauri, headless, and web. Shared orchestration lives in limerick-core; entry-point crates contain only thin wiring (see docs/agent/architecture.md).
/speed./listen hears its weather-, shelter-, season-, and time-aware soundscape; /omen notices a present detail that might be taken as a sign, while refusing to predict the future; and /folklore recalls the location's exact authored tradition without inventing one when no account comes readily to mind. Natural discussion of omens, folklore, or listening to the world can add a brief atmospheric cue while the underlying conversational turn continues. The default-on place-listening flag controls all three.A four-tier simulation that scales hundreds of NPCs at varying fidelity based on proximity to the player:
/v1/messages API, not the OpenAI-compatibility shim), Google Gemini (native Interactions API), OpenRouter, Groq, xAI Grok, Mistral, DeepSeek, Together AI, Custom (any OpenAI-compatible base URL), and a built-in offline Simulator that needs no model download. Google Gemini 3.7 Flash at Low thinking is the default cloud model across Dialogue, Simulation, Intent, and Reaction, with role-specific output caps, implicit-cache usage telemetry, and Google's Standard service tier. (Additional providers are available via mod-loaded configurations — Cohere, GitHub Models, Qwen, Zhipu, OpenCode Zen, and others.) Local profiles are available for macOS (vllm-mlx) and Linux/Windows (vLLM/Ollama), but none currently passes Rundale's production dialogue promotion gate; first-run setup labels them experimental and recommends BYOK cloud for player dialogue./provider.dialogue, /model.intent, /key.simulation).gemini-3.7-flash with the qualified Low-thinking, 4,096-token production profile. A 12-interaction live soak averaged $0.00460 per NPC dialogue interaction (0.46¢; observed range $0.00372–$0.00552) at Google's promotional Standard rates through December 31, 2026. At a human pace of roughly 15 billable NPC exchanges per hour—one every four minutes—that is about $0.069 per gameplay hour; a 10–20-exchange pace is approximately $0.046–$0.092/hour, excluding background-model traffic. The retained multi-family judgments, individual API calls, and latency evidence are published in the local qualification dashboard rather than inferred from general-purpose benchmarks.{mood, action, internal_thought, irish_words}; partial JSON is recovered on truncation.@mention targeting to address a specific NPC in a crowded room.*nods thoughtfully* italicized inline.player-task-progression flag.branches, snapshots, journal_events); readers never block writers, so autosave can fire mid-conversation without hitching./fork <name> creates a non-destructive branch from the current state; /load switches; /branches lists./save and graceful-shutdown autosave on /quit.M./theme — default cream/parchment, Solarized Light, Solarized Dark — driven by CSS custom properties and persisted in localStorage so reloads don't flash the wrong palette.dmooney/rundale by default), embedding the screenshot inline. Per-record buttons attach the exact inference call / event / conversation as context. Every report also carries a "black box" diagnostic payload — the raw LLM prompt/response history, the canonical get_engine_state snapshot, and the last raw user intent — so local-inference drift is reproducible. Also available to auto-QA agents via the limerick_file_bug MCP tool. Gated by the default-on bug-report flag; configured via LIMERICK_BUG_REPORT_TOKEN / LIMERICK_BUG_REPORT_REPO, with LIMERICK_BUG_REPORT_DRY_RUN=1 writing the report to disk instead of filing.limerick_engine_state MCP tool exposes the canonical, deterministic engine state (active scene, clock, weather, player, NPCs, gossip grapevine) so an agent can assert the UI resolved each state transition. The limerick/scripts/limerick-mcp-audit.sh lifecycle script wraps a strict Init → Execute → Validate (UI vs get_engine_state) → Teardown (file a bug on mismatch, kill the backend cleanly) loop. Gated by the default-on engine-state flag.? help, Tab through semantic controls, Enter activate/send, and Esc close a dismissible surface or stop the demo./editor for authoring NPCs, locations, schedules, and mod data without touching JSON directly; see the Limerick Designer section below.crates/limerick-server serves the same Svelte UI over HTTP + WebSocket, one isolated session per limerick_sid cookie.<user-data>/saves/<session_id>/ and survives restarts. The user-data root is platform-native (~/Library/Application Support/Rundale on macOS, $XDG_DATA_HOME/rundale on Linux, %APPDATA%\Rundale on Windows) and named after the active mod's save_root. Override with LIMERICK_SAVES_DIR (saves), LIMERICK_TILE_CACHE_DIR (tile cache), or LIMERICK_USER_DATA_DIR (root)./metrics for auth failures, session counts, and inference call stats.Dockerfile in deploy/.limerick-engine — single-process binary with two modes: --headless (stdin/stdout REPL), --script FILE (deterministic batch driver), no flag (Tauri-launch). HTTP serving is no longer muxed in — limerick-server is now a runnable binary in its own right.--script <file> mode for deterministic JSON-in/JSON-out execution — the backbone of the test harness.limerick-client)limerick binary that talks to a running limerick-server over HTTP — no engine in-process, no game state owned locally.limerick "<cmd>" single-shot, limerick --script <file> for batch fixtures, limerick no-arg REPL, limerick --json "<cmd>" for raw CommandResponse JSON suitable for piping into jq / automation.limerick_sid cookie is saved between runs so subsequent invocations resume the same save branch.mod.toml manifest declares world, NPCs, prompts, anachronisms, festivals, encounters, transport, pronunciations, UI overrides, and loading-screen text.world.json — locations with id, description templates, lat/lon, indoor/public flags, edge connections, mythological significance, and a geo_kind (real / manual / fictional).npcs.json — full NPC schema with personality, six-axis intelligence, home/workplace, mood, and per-season hourly schedules.A GUI editor embedded in the SvelteKit UI at the /editor route, accessible from both the Tauri desktop app and the web server (LIMERICK_ENABLE_EDITOR=1). Follows the mode-parity rule — every editor command is implemented once in limerick-core and wired to both backends.
mods/, switch between them without restarting.{time}, {weather}, {npcs_present}), lat/lon, indoor/public flags, and connection editing with enforced bidirectional edges.WorldGraph::validate() plus orphan NPC homes/workplaces, broken relationship targets, and schedule location refs; click any issue to jump to the field..db save files, branches, and snapshots; view deserialized world state (clock, weather, NPCs, gossip network, conversation log); export a snapshot as a fixture JSON.git diff stays clean even after a no-op round-trip.limerick-geo-tool — Overpass-API CLI that pulls real Irish features into world.json by named area or bounding box, with cached responses, dry-run preview, hand-curated merge mode, and a realign-coords utility for snapping to historical map coordinates.limerick-npc-tool — SQLite-backed NPC builder: bulk-generate parish or county populations with seedable randomness and 1820s demographic weights, query/filter by parish/occupation/tier, edit moods, promote tiers, batch-elaborate backstories with an LLM, validate referential integrity, and export/import JSON. Also splits the monolithic mods/rundale/npcs.json catalogue into per-NPC source files (split-catalog) and re-joins them into a byte-identical canonical file (join-catalog), with a standalone validate-catalog integrity pass.limerick-harness — headless game quality-control harness: runs automated multi-turn playtests where an LLM plays the player and an LLM judges the finished transcript, against limerick-server over HTTP. Each run captures canonical engine state and a rendered telemetry "state-frame" per turn—not a player-visible UI screenshot; evaluates deterministic hard-fail gates (crash / parser-reject / timeout / empty-turn-burn); scores ~7 quality axes (0–100) when gates pass; records findings; and persists everything to SQLite plus on-disk artifacts. The Tauri bridge does not expose the /api/command endpoint this client uses, so desktop/UI quality is covered separately by the live MCP quality harness and Playwright lanes. Run knobs (engine models per category, feature flags, player persona, judge rubric pinned by sha256) are content-addressed for exact A/B comparison and correlated with git history. The player/judge seam runs either deterministic scripted actors (CI, no key) or limerick-inference-backed LLMs (Anthropic / OpenAI-compat / local vllm-mlx). Drive with cargo run -p limerick-harness -- run --config <cfg> --turns N against a running server. For a fully headless real-model game to drive, boot the web server with limerick-server --headless-models (or LIMERICK_HEADLESS_MODELS=1): it detect-reuses (or spawns) the bundled vllm-mlx Qwen two-slot loadout and binds the four inference categories to it, so POST /api/command produces genuine NPC dialogue. The harness applies per-run BYOK model overrides (engine_models.<category>) through runtime slash commands over /api/command, resolving provider keys from the harness environment at apply-time (never persisted into the content-addressed run config). For unattended CI/cron runs, limerick-harness run --player api --judge api is driven solely by env API keys—no Claude Code session, MCP, or subagent queue—and --player/--judge select each actor's driver independently.limerick-scenario — versioned YAML regression runner for agents and CI. Every step drives the shipping limerick_core::game_loop, mocks only inference, and evaluates explicit assertions over emitted IPC events and post-step state. Run all scenarios with just scenario-test or print one JSON report with just scenario-run <file>.test_*.txt fixtures in testing/fixtures/ retain compatibility coverage through structured ScriptResult output. One-off demonstrations are separated under testing/proofs/ and are not counted as regression tests merely because they execute without crashing.Vec<ScriptResult> JSONs in testing/evals/baselines/, with structural rubrics that gate against empty look descriptions, frozen clocks, and anachronistic vocabulary.crates/limerick-core/tests/architecture_fitness.rs mechanically enforces leaf-crate purity (no tauri/axum/tower in shared logic), CLI-vs-leaf duplication bans, and orphaned-module detection. Each failure prints a self-correcting hint.justfile with ~50 recipes grouping build, test, harness, lint, screenshots, deps, geo/NPC tooling, Ollama control, and local CI via act.just witness-scan rejects AI completion stubs (the usual todo! and ellipsis-comment patterns) in changed files.just check-doc-paths ensures every backtick-cited file path in docs/ actually exists.just screenshots).docs/index.md is the master hub — phase status, design overview, ADR index, plans, research, and agent guides.docs/agent/ — slim, indexed reference for AI coding agents (build, architecture, style, gotchas, harness, skills, git workflow), linked from CLAUDE.md and AGENTS.md.Rundale ships with its own reproducible LLM benchmark that scores models as the engine's NPC brain — in-character dialogue, reaction, world simulation, intent, and Gaeilge (Irish-language) fluency — then prices each candidate against real gameplay token volume. The v2 promptfoo suite is the benchmark of record; the v1 harness is archived.
promptfoo/ — the v2 benchmark of record. The v1 harness is archived under rundale-bench/.promptfoo/leaderboard/leaderboard.md + leaderboard.jsonl (append-only history), committed once the first funded run is recorded.Rundale/Limerick is an experiment in building a world too detailed and too improvisational to author by hand. The premise is that AI can simulate a parish of hundreds of NPCs (or more) at varying fidelity, generate their dialogue and reactions on the fly, and remain coherent over long play sessions. I wanted to build something using AI that would be impossible any other way, at least for a solo dev.
To that end, the project is developed entirely by AI coding agents — mostly Claude Code, with Codex and Gemini on specific tasks. Quality control is an evolving combination of agents reviewing each other's work and extensive automated checks — the architecture-fitness tests, gameplay harness, eval rubrics, and snapshot baselines described above are designed to keep AI-written code honest. Human play-testing is the final gate.
Static game content for the Ireland in 1820 setting in mods/rundale/ — NPC personalities, schedules, relationships; location descriptions, lore, pronunciations — is also AI-generated, but human-reviewed before it lands.
Character dialogue, mood, and behaviour are generated in real time by whichever LLM provider you've configured. Every NPC line, gossip rumour, and Tier 2/3 simulation tick comes from a live model call at play time; nothing is pre-baked. Each playthrough is genuinely different, and the dialogue's quality depends on the model you point the engine at.
The workspace ships with a justfile; run just for the full set of recipes.
Requirements: Rust (edition 2024), Node.js (v20+), just (cargo install just or your package manager's equivalent), and an LLM endpoint configured in limerick.toml or .env. See .env.example for environment variables. There is no packaged release yet.
# One-time: install system deps, Rust, Node, and frontend packages
just setup
The default experience is a desktop app.
just run # launches cargo tauri dev
just run-headless # stdin/stdout REPL, engine in-process
just web # Axum web server on :3001 (Svelte UI in browser)
cd limerick && just run-client # thin HTTP REPL against just-web
Single-shot / scripted / JSON modes for limerick-client:
cargo run -p limerick-client -- "look" # one command, formatted output
cargo run -p limerick-client -- --script testing/proofs/play_X.txt # batch fixture
cargo run -p limerick-client -- --json "look" | jq .outcome # raw CommandResponse JSON
See the Ways to run Limerick diagram for how these binaries fit together.
For a shippable .app that ends users can double-click — no Python or
vllm-mlx install required — build the inference bundle first, then
the app:
just build-vllm-mlx-bundle # ~5 min, ~360 MB compressed, Apple Silicon only
cd limerick && cargo tauri build --target aarch64-apple-darwin
The first command materialises a relocatable Python runtime with
vllm-mlx pip-installed straight into its site-packages at
limerick/dist/vllm-mlx/python-runtime/ (using python-build-standalone's
install_only tarball — no venv, since absolute paths in pyvenv.cfg
would break when the bundle moves into Rundale.app/Contents/Resources/).
cargo tauri build then includes that tree under
Rundale.app/Contents/Resources/vllm-mlx/python-runtime/. On first
launch the app detects the bundle and offers the Qwen2.5 local profile
as an experimental option on capable Macs, with its qualification status
shown beside the BYOK recommendation. Choosing local downloads the weights
with a live progress bar.
CI driver: .github/workflows/build-vllm-mlx-bundle.yml (manual
trigger, uploads the bundle as an artifact). For dev iteration on
cargo tauri dev, you can skip the bundle build — the runtime falls
through to a PATH-installed vllm-mlx (i.e. uv tool install vllm-mlx).
One engine, three thin entry points, fourteen backend-agnostic leaf crates. The full crate-by-crate map lives in docs/agent/architecture.md.
flowchart TB
subgraph clients["Frontends & clients"]
UI["Svelte 5 UI<br/>limerick/apps/ui<br/>(one transport.ts for both backends)"]
CLI["Limerick CLI client<br/>limerick-client"]
MCP["limerick-mcp<br/>MCP bridge for AI agents"]
end
subgraph entry["Runtime entry points (thin adapters, mode parity)"]
TAURI["limerick-tauri<br/>Tauri 2 desktop"]
SERVER["limerick-server<br/>Axum HTTP + WS<br/>(sessions, auth, idempotency)"]
ENGINE["limerick-engine<br/>headless REPL / --script / Tauri launch"]
end
CORE["limerick-core — composition + orchestration<br/>ipc/ • game_loop/ • game_session<br/>event_bus • prompts<br/>(re-exports limerick-mod as game_mod,<br/>limerick-editor as editor,<br/>limerick-chronicle as character_log/location_log/chat_transcript,<br/>limerick-diagnostics as debug_snapshot)"]
subgraph leaf["Shared leaf crates (backend-agnostic, enforced)"]
WORLD["limerick-world<br/>graph, movement, weather, geo"]
NPC["limerick-npc<br/>cognitive LOD tiers 1–4, mood,<br/>memory, ticks, gossip<br/>(tier 4 = CPU rules, no LLM)"]
INPUT["limerick-input<br/>parsing, intent (local + LLM)"]
INFER["limerick-inference<br/>queue, priority lanes, worker, validation"]
PROVIDERS["limerick-providers<br/>provider HTTP clients, simulator/mock,<br/>AnyClient dispatch, rate limits"]
SETUP["limerick-setup<br/>GPU detect, model select,<br/>Ollama/vllm bootstrap"]
PERSIST["limerick-persistence<br/>SQLite WAL, journal, snapshots, branches"]
CONFIG["limerick-config<br/>TOML + env + flags"]
PALETTE["limerick-palette<br/>day/night palette"]
MOD["limerick-mod<br/>content-mod loader<br/>(manifest, discovery, world bridge)"]
EDITOR["limerick-editor<br/>Designer backend<br/>(mod I/O, validation, persistence, save inspect)"]
CHRONICLE["limerick-chronicle<br/>on-disk chronicle writers<br/>(character/location markdown logs, chat transcript)"]
DIAG["limerick-diagnostics<br/>debug-snapshot builders +<br/>bug-report orchestration"]
TYPES["limerick-types<br/>ids, time, events, errors (zero internal deps)"]
end
subgraph external["Content & external systems"]
MODS[("mods/rundale<br/>world.json, npcs.json, prompts…")]
DB[("SQLite saves<br/>per-user data dir")]
LLM["LLM providers<br/>Ollama / OpenAI-compat / Anthropic / simulator"]
end
UI -- "Tauri IPC invoke/listen" --> TAURI
UI -- "fetch + WebSocket" --> SERVER
CLI -- "POST /api/command" --> SERVER
MCP -- "HTTP :3030" --> SERVER
TAURI -- "handle_command + EventEmitter" --> CORE
SERVER --> CORE
ENGINE --> CORE
CORE --> WORLD & NPC & INPUT & INFER & PERSIST & CONFIG & PALETTE & MOD & EDITOR & CHRONICLE & DIAG & TYPES
INPUT -. "intent LLM" .-> INFER
NPC -. "T1 dialogue • T2 group sim + gossip • T3 batch sim" .-> INFER
NPC -.-> WORLD
PERSIST -.-> NPC
MOD -.-> WORLD
EDITOR -. "mod I/O + validation" .-> MOD
NPC -. "all leaves depend on types" .-> TYPES
MOD -- "mod.toml manifest + validation" --> MODS
PERSIST --> DB
INFER -- "dispatch via AnyClient" --> PROVIDERS
PROVIDERS --> LLM
classDef clientNode fill:#d7e7f7,stroke:#4a7aab,color:#1f2328
classDef entryNode fill:#fae3bd,stroke:#c08a2e,color:#1f2328
classDef coreNode fill:#e3d3f4,stroke:#8a5fb8,color:#1f2328
classDef leafNode fill:#cdeccf,stroke:#4f9457,color:#1f2328
classDef extNode fill:#ffffff,stroke:#777777,color:#1f2328
class UI,CLI,MCP clientNode
class TAURI,SERVER,ENGINE entryNode
class CORE coreNode
class WORLD,NPC,INPUT,INFER,PROVIDERS,SETUP,PERSIST,CONFIG,PALETTE,MOD,EDITOR,CHRONICLE,DIAG,TYPES leafNode
class MODS,DB,LLM extNode
style clients fill:#eef4fb,stroke:#9db8d4,color:#1f2328
style entry fill:#fdf3e3,stroke:#d8b873,color:#1f2328
style leaf fill:#e9f6ea,stroke:#9ccca0,color:#1f2328
style external fill:#f6f6f6,stroke:#bbbbbb,color:#1f2328
limerick/
crates/ 24 workspace members (runtime, scenario/harness tools, and leaf logic crates)
apps/ui/ Svelte 5 + TypeScript frontend
testing/fixtures/ scripted gameplay fixtures
scripts/ Maintenance and quality gate scripts
mods/rundale/ Rundale game content (world, NPCs, prompts, lore)
deploy/ Dockerfile
docs/ design, ADRs, plans, research, agent guides
justfile Top-level proxies for common tasks
The game icon was generated with ChatGPT (OpenAI image generation) from a hand-written prompt and is shipped as-is.
| Start here | What you'll find |
|---|---|
| docs/index.md | Master hub — phase status, links to everything |
| docs/repository-layout.md | Top-level directory tree and crate index |
| docs/troubleshooting.md | Bug reporting + inference-log artefact guide |
| docs/design/overview.md | Architecture, tech stack, module tree, LLM providers |
| docs/graphics-v2/README.md | Visual-client research: notebook UI, graphics pipelines, assets, and evidence |
| docs/requirements/roadmap.md | Per-item status tracking across all phases |
| docs/research/README.md | Research documents covering life in 1820's Ireland |
| docs/adr/README.md | Architecture decision records and rationale |
| AGENTS.md | Agent guide — index into docs/agent/ for build, style, and gotchas |
Rundale on the Limerick engine is © 2026 Dave Mooney and is licensed under the
GNU General Public License v3.0 (GPL-3.0-only). Source code is
free to use, modify, and redistribute under the terms of that licence.
"Rundale" and "Limerick" are unregistered trademarks of Dave Mooney. The
GPL covers source reuse but not the project names or logos: forks must
rename. (A formal trademark policy lives at TRADEMARK.md once published.)
Rundale is built on a stack of excellent open-source projects, including
Rust, Tokio,
Axum, Tauri,
Svelte / SvelteKit,
MapLibre GL JS, SQLite,
and Phosphor Icons. Full attribution with
licence texts is in THIRD_PARTY_NOTICES.md; run
just notices to regenerate the exhaustive transitive list.
Map data © OpenStreetMap contributors, licensed under the Open Database Licence 1.0. Historic 6″ Ordnance Survey Ireland tiles (1829–1842) reproduced with the permission of the National Library of Scotland, licensed under CC-BY. UI icons use Phosphor Icons under MIT. Map labels use Open Sans under the SIL Open Font License 1.1; its generated MapLibre glyph ranges and licence are bundled for offline web and desktop rendering.
1,159 commits
22 commits
Rust
47.4%
HTML
34.8%
TypeScript
5.9%
Python
4.9%
JavaScript
3.2%
Svelte
2.0%
Shell
1.2%