Work xrpg
library

XRPG Engine

An AI-narrated browser RPG framework. One codebase, many worlds.

XRPG is a self-contained framework for AI-narrated browser RPGs. Passkey-only auth, deterministic ruleset, deep character progression, and a multi-instance deployment model — one repo ships Tulpa (dark high fantasy), Grim Syndicate (cyberpunk), and any future world that wants to inherit the bones. The game on top brings narration, art, and tone; the engine handles everything underneath.

Last updateMar 17, 2026 PrimaryPython
  • Python
  • FastAPI
  • PostgreSQL
  • SQLAlchemy
  • Jinja2
  • WebAuthn
  • OpenRouter
  • Apache
  • systemd
XRPG Engine — An AI-narrated browser RPG framework. One codebase, many worlds.
XRPG Engine media
XRPG Engine media

XRPG is a from-scratch framework for the browser — the same skeleton powers Tulpa (dark high fantasy), Grim Syndicate (cyberpunk), and any future world that wants to inherit the bones. The contract is simple: the engine handles auth, character state, theming, persistence, the deterministic side of the ruleset, and the LLM-narration plumbing; the game on top brings tone, art, and the world's specific rules.

Multi-instance, one codebase

A single git repo deploys as many instances as you want — each with its own database, branding, terminology, port, and theme. Configuration lives in a per-deployment .instance file plus a metagame_config table; the codebase reads which instance it's running as at boot and adjusts. Adding a new world is a config-and-art exercise, not a fork.

What the engine actually gives you

  • Passkey-only authentication via WebAuthn — passwordless login, registration, and recovery wired to rate limiting and suspicious-activity detection. No passwords to leak, no resets to engineer.
  • Deterministic ruleset — stats roll, loot drops with affixes and rarity tiers, energy decays, levels gate progress. The narration layer never has to be trusted with the math; it gets to spin the colour around the dice.
  • Persistent world state — locations, NPCs, scenarios, and items remember what happened. Wander off the map and the world grows to fill the space — for you and for whoever follows.
  • Energy economy instead of HP. Exploration, combat, and spellcasting all draw from a token-aware pool that ties gameplay directly to the real cost of LLM inference. Run dry and the campaign sleeps until you recover.
  • PvP arena with power-level matchmaking, multi-round combat, and chronicles you can re-read. Built once at the engine layer, available to every instance.
  • Admin UI for keys + config — model routing, system prompts, content tuning, encrypted API keys stored in the database (never in config files). Run the engine on free OpenRouter models or paid ones, flip per-instance.

How it's built

Python 3.10 + FastAPI on PostgreSQL (async via SQLAlchemy + asyncpg), rendering Jinja2 templates with vanilla JS — no React, no bundler, no build step. The narrator pipeline talks to OpenRouter; auth is WebAuthn passkeys via the webauthn package. Apache fronts each instance as a reverse proxy with Let's Encrypt SSL; systemd runs each one as an unprivileged user with its own database.

The two worlds shipped on it

Tulpa is the long-form text RPG — improvised AI dungeons, persistent world memory, the works. Read the full piece in the Tulpa exhibit. Grim Syndicate is the cyberpunk counterpart — same engine, different tone, different rules layered on top. The gateway at xrpg.win is where both surface to the wider world.

Straight from the source

The project's own README.

Rendered in place — every link, image, and code block carried over from the repo. The page below is what a contributor would see opening the project for the first time.

Multi-Instance Architecture

Overview

XRPG Engine supports multiple deployments from a single git repository. Each deployment is an instance with its own database, branding, terminology, and theme colors — all configured at runtime via a .instance file and the metagame_config database table.

Current Instances

Instance Server Path Port Service DB Domain
Tulpa Server A /var/www/tulpa 8420 tulpa tulpa tulpa.gamingworld.uk
XRPG Engine Server B /srv/xrpg.gamingworld.uk 8421 xrpg db_xrpg xrpg.gamingworld.uk
Grim Syndicate Server B /srv/grimsyndicate.gamingworld.uk 8422 grimsyndicate db_grimsyndicate grimsyndicate.gamingworld.uk

Note: Actual server IPs are stored in each instance's .deploy_targets file (gitignored).

.instance File

Each deployment has a .instance JSON file in its project root (gitignored). This file tells the codebase and scripts which instance they're running as.

{
    "app_name": "Grim Syndicate",
    "domain": "grimsyndicate.gamingworld.uk",
    "port": 8422,
    "db_name": "db_grimsyndicate",
    "db_user": "grimsyndicate",
    "cookie_name": "grim_session",
    "service_name": "grimsyndicate"
}

Keys

Key Required Description
app_name Yes Display name used in templates, nav, footer
domain Yes Public domain for URLs and CORS
port Yes Port uvicorn listens on
db_name Yes PostgreSQL database name
db_user Yes PostgreSQL user
cookie_name Yes Session cookie name (must be unique per instance on same server)
service_name Yes systemd service name

If .instance is missing, the app reads from app/core/config.py defaults (XRPG Engine generic).

Metagame Config System

The metagame_config database table stores per-instance settings that override code defaults. This powers:

  • Branding: logos, hero backgrounds, splash images, favicons, OG images
  • Content: landing page copy (3 blurbs)
  • Terminology: entity terms, currency names, magic terms
  • SEO: meta descriptions
  • Theme colors: per-theme accent, background, and text colors

Fallback Chain

  1. Database (metagame_config table) — checked on every page load via get_metagame(db)
  2. Code defaults (METAGAME_DEFAULTS in seed_metagame.py) — white-label greyscale
  3. CSS defaults (theme.css) — neutral greyscale variables

Managing Config

  • Admin UI: Visit /admin?tab=metagame to edit all keys in-browser
  • Seed script: seed_metagame() runs on startup — only inserts keys that don't exist yet (never overwrites)
  • Direct SQL: UPDATE metagame_config SET value = '...' WHERE key = '...';

Theme Customization

How It Works

  1. theme.css defines greyscale defaults for all CSS variables (white-label baseline)
  2. base.html checks metagame theme_* keys and injects a <style> block that overrides CSS variables
  3. Each instance sets its own accent colors in the DB

Theme Keys

Key Description
theme_dark_accent_1 Dark mode primary accent hex (e.g., #c8a44e)
theme_dark_bg_primary Dark mode background hex
theme_dark_text_primary Dark mode text hex
theme_dark_text_accent Dark mode accent text hex
theme_light_accent_1 Light mode primary accent hex (e.g., #3d8eb9)
theme_light_bg_primary Light mode background hex
theme_light_text_primary Light mode text hex
theme_light_text_accent Light mode accent text hex

If all theme keys are empty, the instance uses greyscale CSS defaults.

Instance Palettes

  • Tulpa: Amber/gold dark (#c8a44e), blue/ice light (#3d8eb9)
  • Grim Syndicate: Matrix neon green dark (#00ff41), forest green light (#1a5c2a)
  • XRPG Engine: Greyscale (no overrides)

Asset Management

Branding Images

Instance-specific images (logos, splash art) are stored in app/static/images/ which is gitignored. Each instance has its own set of images.

Metagame keys control which images appear:

  • logo_dark_path / logo_light_path — nav and footer logos
  • hero_bg_dark_path / hero_bg_light_path — landing page hero background
  • splash_image_path — bottom splash image on landing page

Fallbacks

If image paths are empty:

  • Nav/footer logos: Lucide gamepad-2 icon is shown instead
  • Hero background: The gradient overlay handles the look (no image needed)
  • Splash image: Section is hidden entirely

Adding a New Instance

  1. Create the deployment directory and clone the repo
  2. Create .instance with the instance's config
  3. Create the PostgreSQL database and user
  4. Create a systemd service (copy from existing, change port/paths)
  5. Set up the reverse proxy (Nginx/Apache vhost)
  6. Run bash scripts/install.sh to set up the venv
  7. Start the service — tables are auto-created, metagame is seeded with defaults
  8. Customize via admin UI at /admin?tab=metagame — set theme colors, terminology, branding paths
  9. Generate/upload branding images to app/static/images/

CI/CD Workflow

Single Instance Deploy

sudo bash scripts/deploy.sh      # Restart current instance
sudo bash scripts/auto_deploy.sh  # Pull + install + restart

Deploy All Instances

bash scripts/sync_all.sh  # Push to origin + deploy to all instances

This pushes to git, then runs auto_deploy.sh on each local and remote instance.

Instance-Aware Scripts

All scripts in scripts/ source _instance.sh which reads .instance and exports:

  • $PROJECT_DIR — project root path
  • $INST_APP_NAME — display name
  • $INST_PORT — port number
  • $INST_DB_NAME — database name
  • $INST_DB_USER — database user
  • $INST_SERVICE — systemd service name

This means deploy.sh, check.sh, backup.sh, etc. automatically target the correct instance based on which directory they're run from.

Build something like this

Want a tool like this for your shop?

We've shipped this kind of thing before. Twenty-minute intro call, no slides.