A self-hosted worldbuilding and lore management system for the Neon & Dragons tabletop RPG campaign. Organize your entire game world — characters, locations, factions, events, items, creatures, and more — with built-in AI assistance, image generation, interactive maps, and visual relationship boards.
New to running the app day-to-day? docs/wiki/Home.md is a full usage guide for GMs and players — worldbuilding, characters, maps, sessions, AI tools, settings — everything below the install steps.
AI agents / assistants working with this repo: see AGENTS.md first — it covers content-creation via the general importer (
docs/AI_ENTITY_GUIDE.md,docs/AI_SCHEMATIC_GUIDE.md) and conventions to follow if modifying the app code. For a full list of every HTTP route and MCP tool the app exposes, see docs/API_REFERENCE.md.
- Multi-world support — create and switch between separate game worlds, each with its own colour accent, font, and font size
- 10 entity types — Characters, Locations, Organizations, Creatures, Events, Items, Feats, Notes, Races, Professions — each with TTRPG-specific subtypes, plus GM-defined custom kinds
- Entity relationships — link any entity to any other; navigate connections from the detail page
- Automatic entity linking — mentioning another entity's name in a body or note wikilinks it automatically, with no manual
[Name](url)needed - Entity notes — attach multiple discrete notes to any entity, each independently hideable/un-hideable from players regardless of the entity's own visibility (e.g. reveal one detail about a location while keeping the rest secret). Type one directly, or import from a file —
.md/.txt/.pdfbecome plain text,.html/.htmconvert to markdown by default (or keep original formatting, sanitized, with "preserve original formatting" checked), images become an embedded note - Custom entity fields & stat blocks — Field Templates add structured data (age, title, alive/dead/missing status, a repeatable combat stat block, or any fields you define) to Characters, Creatures, or any other entity kind, on top of the usual free-text body — ships with built-in "NPC Details" and "Stat Block" templates (see Entity Field Templates)
- Folder organization — hierarchical folders per entity type for large lore collections; the entity form offers a picker of existing folders (still free-text for new ones), and any folder you're browsing can be renamed/moved or removed (entities become Unfiled) from its breadcrumb
- Image attachments — upload JPG/PNG/GIF/WebP/AVIF images to any entity (SVG is deliberately rejected — it can carry
<script>and would be served from the app's own origin) - Rules viewer — built-in core rules rendered from Markdown with auto-generated table of contents
- Interactive maps — add custom markers and region overlays to map images
- Schematics — SVG-based canvas editor for drawing station/dungeon layouts
- Investment boards — node-and-edge graph boards for plotting organization structures and story threads
- AI chat — Ollama LLM integration with streaming responses and world-lore RAG context
- Talk to NPCs — persistent in-character conversations with any entity (saved per user × NPC, so every player has their own private thread); GMs get model/thinking/lore-retrieval toggles, players get it under the same per-world "players can use Ask AI" opt-in as the other AI surfaces
- AI Models tab — download, manage, and delete Ollama models directly from the app UI with live progress bars; popular model quick-picks included
- Ollama tuning from the browser — full per-request generation options (temperature, sampling, context length, and more) plus server-level tuning (flash attention, KV cache quantization, parallelism, GPU selection, ...) from Settings → System, no
.envediting required; a "Detected hardware" panel reads the host's CPU/RAM/GPU and recommends a starting-point configuration per installed model (see Deploying nd-world) - AI image generation — SwarmUI or ComfyUI backend with sampler/scheduler, LoRA, VAE, CLIP skip, upscaling, img2img, batch output, and generation history with parameter reuse
- Image Studio — embedded SwarmUI iframe at
/imagestudio - AI video generation — generate clips through Unsloth Studio's
/v1/videosas durable background jobs (survive page close and server restarts — a restart re-attaches to the Studio-side generation instead of restarting it); finished clips land in the Video library as GM-only drafts - Universal character sheets — fully configurable stats, skills, and currency (N&D defaults: STR/DEX/BOD/PER/WIL/INT/CHA/ITU); optional secondary resource tracker
- Rules-driven Player Character creation wizard — guided Race → Profession → Stats (point-buy) → Feats → Equipment flow implementing the Neon & Dragons Core Rules character creation procedure, backed by the same race/profession/feat/equipment catalog used by the NeonDragonsApp Android app and NeonDragonsEditor desktop tool
- Custom character systems — not locked into N&D: a Sheet Template can fully replace the N&D sheet with its own fields (including repeatable lists like abilities or a session log) for running a different TTRPG entirely in the same world — ships with a built-in Asterion system alongside N&D (see Custom Character Systems)
.ndccharacter export — export any Player Character as a.ndcfile importable directly into both the NeonDragonsApp Android app and the NeonDragonsEditor desktop editor (see Character creation wizard & export)- GM & player accounts — one GM account per deployment; invite players by link, each managing their own character. GMs can hide spoiler content (from everyone, or from all but a hand-picked subset of players), toggle party-wide character visibility per world, and send private per-player notes (see Accounts, Invites & Going Public)
- Full-text search — across names, tags, summaries, and body text
- JSON export / import — complete world backup and restore with embedded images
- Mobile-responsive UI — hamburger nav, touch-friendly targets, stacking layouts on phones and tablets
- Dark / Half-dark / Light themes — toggle in the nav bar (next to your account badge); choice is remembered per browser. A world's custom accent color still applies on top of whichever theme you pick
- Requirements
- Install on Linux
- Install on Windows
- Install on TrueNAS SCALE
- Environment Variables
- AI Setup
- Data & Backups
- Character Creation Wizard & Export
- Custom Character Systems
- Entity Field Templates
- Accounts, Invites & Going Public
- Project Structure
- Ports Reference
- Troubleshooting
- License
| Requirement | Notes |
|---|---|
| Docker Engine + Docker Compose v2 | Required for all install methods |
| Git | For cloning the repository |
| GPU | Optional — CPU-only mode works for Ollama and SwarmUI |
Ollama and SwarmUI are included in the Docker stack but off by default — see AI Setup to enable one or both; no separate installation needed either way.
These instructions work on Ubuntu 22.04/24.04, Debian 12, and most other modern distros.
Fast path: if Docker is already installed,
bash scripts/setup.shdoes Steps 2–6 below for you (creates.env, sets up your GM login, starts the stack) — see Accounts, Invites & Going Public and docs/DEPLOYMENT.md.
# Remove old versions if present
sudo apt remove docker docker-engine docker.io containerd runc 2>/dev/null
# Install prerequisites
sudo apt update
sudo apt install -y ca-certificates curl gnupg
# Add Docker's GPG key and repository
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] \
https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
# Install Docker Engine and Compose plugin
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
# Allow running docker without sudo (log out and back in after this)
sudo usermod -aG docker $USER
newgrp dockerVerify the installation:
docker --version # Docker version 26.x or newer
docker compose version # Docker Compose version v2.x or newersudo apt install -y git
git clone https://github.com/8bit-boom/nd-world.git
cd nd-worldnano docker-compose.ymlKey settings to review:
services:
world:
ports:
- "8080:8000" # change 8080 if that port is already in use
environment:
OLLAMA_MODEL: gemma4:26b # model to use for AI chat
SWARMUI_EXTERNAL_URL: "" # set to http://<your-ip>:7801 for Image Studio iframeNote:
OLLAMA_URLis pre-configured tohttp://ollama:11434pointing at the bundled Ollama container. You only need to change it if you want to use a different Ollama instance.
docker compose up -dBy default this starts just nd-world (the app) — SwarmUI (image gen) and
Ollama (AI chat) are optional and skipped unless enabled (see
AI Setup — set COMPOSE_PROFILES in .env, or use bash scripts/setup.sh, which asks).
Watch the logs:
docker compose logs -fIf you enabled SwarmUI, it performs a first-run setup on initial boot (downloads the ComfyUI backend). This can take 5–15 minutes depending on your connection.
Open the app at http://localhost:8080 and go to AI → 🤖 Models tab. Click any model in the Popular Models grid (e.g. Llama 3.2 3B for a fast 2 GB download, or Gemma 4 26B for the best quality) — the progress bar shows download percentage and GB transferred. Once complete, the model is ready for chat.
Alternatively, pull from the command line:
docker compose exec ollama ollama pull gemma4:26b- N&D World: http://localhost:8080
- SwarmUI: http://localhost:7801
- Ollama API: http://localhost:11434
To access from other devices on your network, replace localhost with your machine's LAN IP:
ip addr show | grep "inet " | grep -v 127.0.0.1
# example: inet 192.168.1.100/24Also set SWARMUI_EXTERNAL_URL: "http://192.168.1.100:7801" in docker-compose.yml to enable the Image Studio iframe, then restart: docker compose restart world.
Ollama's text models and SwarmUI's image checkpoints/LoRAs/VAEs both live under one shared host directory — ./ai-models/ next to docker-compose.yml by default, or wherever AI_MODELS_DIR in .env points (see Environment Variables) — instead of separate Docker-managed volumes, so there's one place to drop files, back up, or point at models you already have elsewhere. Just copy checkpoint files in directly:
cp your-model.safetensors ./ai-models/swarmui/Stable-Diffusion/Then open the AI → Image Gen tab, click the model dropdown refresh button — your model will appear.
Upgrading an existing install: if you already had SwarmUI/Ollama running before this change, your models are sitting in the old
nd-world_swarmui-models/nd-world_ollama-dataDocker volumes, whichdocker compose up -dwill no longer mount. Copy them into the new location once, then the old volumes can be removed:mkdir -p ai-models/swarmui ai-models/ollama docker run --rm -v nd-world_swarmui-models:/from -v "$(pwd)/ai-models/swarmui":/to alpine cp -a /from/. /to/ docker run --rm -v nd-world_ollama-data:/from -v "$(pwd)/ai-models/ollama":/to alpine cp -a /from/. /to/ docker compose up -d docker volume rm nd-world_swarmui-models nd-world_ollama-data # once you've confirmed everything still works
docker compose stop # stop all containers
docker compose start # start them again
docker compose down # stop and remove containers (data volumes are kept)
docker compose down -v # ⚠ also removes volumes — deletes all data
docker compose pull # pull latest images
docker compose up -d --build # rebuild and restart after code changesAn update no longer discards whatever background jobs were mid-flight — audio transcription/summarization checkpoints per chunk and resumes automatically on the next boot; an in-flight image generation or chat completion restarts from the same saved request. See docs/DEPLOYMENT.md for the details.
These instructions cover Windows 10 (21H2+) and Windows 11 using Docker Desktop with WSL 2.
Open PowerShell as Administrator and run:
wsl --installReboot when prompted. After reboot, a terminal will open to finish Ubuntu setup — create a Linux username and password.
Verify WSL 2 is the default:
wsl --set-default-version 2
wsl --list --verbose- Download Docker Desktop from docker.com/products/docker-desktop
- Run the installer — keep "Use WSL 2 instead of Hyper-V" checked
- After installation, start Docker Desktop and wait for the Docker engine to show Running in the taskbar
- Open Settings → Resources → WSL Integration and enable integration for your Ubuntu distribution
Verify in a PowerShell or WSL terminal:
docker --version
docker compose versionDownload and install Git from git-scm.com.
Then in Git Bash, PowerShell, or WSL:
git clone https://github.com/8bit-boom/nd-world.git
cd nd-worldTip: For best performance, clone inside the WSL filesystem: open WSL terminal →
cd ~→ then clone.
Open docker-compose.yml in VS Code or Notepad:
services:
world:
ports:
- "8080:8000"
environment:
OLLAMA_MODEL: gemma4:26b
SWARMUI_EXTERNAL_URL: "http://localhost:7801" # localhost works on Windows for iframeWindows note:
host.docker.internalis automatically available on Docker Desktop for Windows — no extra configuration needed. The bundled Ollama container connects internally viahttp://ollama:11434.
docker compose up -dCheck all three containers started:
docker compose ps
# nd-world, swarmui, ollama should all show "Up"Open http://localhost:8080 → AI → 🤖 Models → click any popular model chip to download it with live progress.
Or via terminal:
docker compose exec ollama ollama pull gemma4:26b- N&D World: http://localhost:8080
- SwarmUI: http://localhost:7801
To make the app accessible to other devices on your network:
- Find your LAN IP: PowerShell →
ipconfig→ look for IPv4 Address under your adapter - Open Windows Firewall for the ports:
# Run as Administrator New-NetFirewallRule -DisplayName "nd-world" -Direction Inbound -Protocol TCP -LocalPort 8080 -Action Allow New-NetFirewallRule -DisplayName "SwarmUI" -Direction Inbound -Protocol TCP -LocalPort 7801 -Action Allow New-NetFirewallRule -DisplayName "Ollama" -Direction Inbound -Protocol TCP -LocalPort 11434 -Action Allow
docker compose stop # stop containers
docker compose start # start them again
docker compose down # remove containers (volumes kept)
docker compose up -d --build # rebuild after code changesOr manage containers visually via the Docker Desktop GUI under the Containers tab.
TrueNAS SCALE uses truenas-compose.yml which pulls pre-built images from GitHub Container Registry and stores data in host bind-mount paths on your ZFS pool.
In the TrueNAS web UI go to Datasets and create the following datasets under your pool (e.g., DeadPool):
| Dataset path | Purpose |
|---|---|
DeadPool/apps/nd-world |
nd-world database and uploads |
DeadPool/apps/swarmui/data |
SwarmUI configuration |
DeadPool/apps/swarmui/models |
Checkpoint/LoRA model files |
DeadPool/apps/swarmui/dlbackend |
ComfyUI backend (auto-downloaded) |
DeadPool/apps/ollama |
Ollama model storage |
DeadPool/apps/nd-world-ollama-config |
Shared with nd-world — lets Settings → System save Ollama server tuning without editing this file (see docs/DEPLOYMENT.md) |
Or create them all via SSH shell:
mkdir -p /mnt/DeadPool/apps/nd-world mkdir -p /mnt/DeadPool/apps/swarmui/{data,models,dlbackend} mkdir -p /mnt/DeadPool/apps/ollama mkdir -p /mnt/DeadPool/apps/nd-world-ollama-config
If your pool is named differently, search and replace DeadPool throughout truenas-compose.yml.
nano truenas-compose.ymlSet your TrueNAS IP address:
services:
world:
environment:
OLLAMA_MODEL: gemma4:26b
SWARMUI_EXTERNAL_URL: "http://192.168.1.xxx:7801" # your TrueNAS IPIf your datasets are on a different pool or path, update all volume bind mounts (search and replace /mnt/DeadPool).
- In the TrueNAS SCALE web UI, go to Apps → Discover Apps
- Click Custom App (top right)
- Fill in the form:
- Application Name:
nd-world - Custom Config: switch to Compose mode
- Paste the full contents of your edited
truenas-compose.yml
- Application Name:
- Click Install
TrueNAS will pull the images and start all containers (world, swarmui, ollama, watchtower).
After deployment, TrueNAS registers portal buttons automatically from the net.ix-portals.* labels:
| Service | Port | URL |
|---|---|---|
| nd-world | 8087 | http://<truenas-ip>:8087 |
| SwarmUI | 7801 | http://<truenas-ip>:7801 |
| Ollama | 11434 | http://<truenas-ip>:11434 |
These appear as clickable portal buttons on the app's tile in Apps → Installed Apps.
Open nd-world → AI → 🤖 Models tab → click a popular model to download it, or:
docker exec -it <ollama-container-name> ollama pull gemma4:26bCopy .safetensors or .ckpt files into the models directory:
cp your-model.safetensors /mnt/DeadPool/apps/swarmui/models/Stable-Diffusion/Then open the AI → Image Gen tab and refresh the model dropdown.
With the legacy SwarmUI stack deprecated, this helper script was removed. Install
ComfyUI-Manager manually if you still run the legacy swarmui profile.
truenas-compose.yml includes Watchtower, which checks every 5 minutes for new image versions and restarts affected containers automatically. To disable auto-updates, remove or comment out the watchtower service block.
| Variable | Default | Description |
|---|---|---|
DB_PATH |
/data/world.db |
Path to the SQLite database file |
OLLAMA_URL |
http://ollama:11434 |
URL of the Ollama instance (pre-configured to bundled container) — legacy backend, used only while UNSLOTH_API_KEY is unset |
OLLAMA_MODEL |
gemma4:26b |
Default LLM model for AI chat (legacy Ollama backend) |
UNSLOTH_URL |
http://unsloth:8000 |
Unsloth Studio URL — the single AI backend (chat + image gen) when an API key is set |
UNSLOTH_API_KEY |
(empty) | Studio API key (create in Studio → Settings → API). Setting this switches AI chat and image generation to Unsloth; blank = legacy Ollama/SwarmUI backends |
UNSLOTH_MODEL |
unsloth/gemma-4-26B-A4B-it-GGUF |
Chat model — a repo id downloaded in Studio's Model Hub |
UNSLOTH_IMAGE_MODEL |
unsloth/z-image-turbo-GGUF |
Image model downloaded in Studio's Model Hub |
UNSLOTH_STUDIO_PASSWORD |
(empty) | Login password for the Studio web UI (port 8000) |
LLM_CONTEXT_TOKENS |
16384 |
The load-time context window the Studio chat model was loaded with — sizes the recap chunking math (llama.cpp fixes context at load, unlike Ollama's per-request num_ctx) |
IMAGEGEN_TYPE |
swarmui |
Legacy image generator backend: swarmui or comfyui. Ignored while the Unsloth backend is active |
IMAGEGEN_URL |
http://swarmui:7801 |
Internal URL of the image generator API |
SWARMUI_EXTERNAL_URL |
(empty) | Browser-accessible SwarmUI URL for the Image Studio iframe |
ND_ALLOWED_HOSTS |
* |
Comma-separated allowed Host headers (security hardening) |
SECRET_KEY |
(random each restart) | Signs session cookies — set a fixed value in production (openssl rand -hex 32), or logins won't survive a restart |
GM_EMAIL / GM_PASSWORD |
(empty) | Bootstraps the GM account on first start. Leave blank if the GM account already exists |
GM_NAME |
GM |
Display name for the bootstrapped GM account |
GM_PASSWORD_RESET |
(empty) | Set (alongside GM_EMAIL) and restart to force-reset a locked-out GM's password — the GM has no one else with admin rights over their own account, unlike a player (who the GM can reset from the world's Members list). Logs out every session/trusted device for that account. Remove it after the restart — it re-applies (and re-logs-everyone-out) on every boot while set |
COOKIE_SECURE |
false |
Set true once served over HTTPS (see Accounts, Invites & Going Public) |
COMPOSE_PROFILES |
(empty) | Not read by the app itself — Docker Compose reads it to decide which optional services to start. Empty starts just world; set any comma-separated combination of unsloth, android, editor — or the legacy pair ollama,swarmui (kept until the migration cutover; don't mix with unsloth) |
AI_MODELS_DIR |
./ai-models |
Not read by the app itself — Docker Compose reads it to pick where Ollama's text models and SwarmUI's image checkpoints/LoRAs/VAEs are stored on the host (in ollama/ and swarmui/ subfolders), instead of separate Docker-managed volumes. Only matters if the corresponding profile(s) are enabled |
Unsloth Studio is the primary AI backend — it serves both AI chat and image generation — and speech-to-text for audio attachments and session recordings — from a single container. The legacy Ollama (chat) + SwarmUI (image) pair is still shipped for rollback, but everything below marked "legacy" is scheduled for removal after the migration cutover — see docs/UNSLOTH_PHASE0_FINDINGS.md.
Unsloth Studio is defined in all compose files behind the unsloth Compose
profile — enable it via bash scripts/setup.sh or set COMPOSE_PROFILES=unsloth
in .env and run docker compose up -d. The first boot downloads the Studio
image (several GB); chat and image models are then fetched from the Studio's
Model Hub.
One-time setup:
- Open the Studio UI (port 8000) and log in (username
unsloth, password fromUNSLOTH_STUDIO_PASSWORD). - Studio → Settings → API → create an API key.
- Paste the key into nd-world → ⚙️ Settings → Unsloth (AI backend) → API key. The moment a key is saved, AI chat and image generation switch to Unsloth automatically (no restart); clear the key to fall back to the legacy Ollama/SwarmUI backends.
- Download models in Studio's Model Hub. Defaults (overridable via
UNSLOTH_MODEL/UNSLOTH_IMAGE_MODEL):- Chat:
unsloth/gemma-4-26B-A4B-it-GGUF - Images:
unsloth/z-image-turbo-GGUF
- Chat:
Notes:
- Context size is fixed at model load (llama.cpp semantics): set
Context tokens in Settings to match what the model was loaded with
(
LLM_CONTEXT_TOKENS, default 16384) so recap chunking math is correct. - Idle models can be unloaded from the Studio UI; with an API key set the app reloads on demand.
- On a Tesla V100 (sm_70), pin the Studio image tag —
:latestmay not ship sm_70 kernels (see findings doc, I-7) — and use fp16 everywhere (see docs/GPU_SETUP.md). - API keys do not survive Studio container recreation — re-create the key and update Settings after a rebuild.
Unsloth Studio and the legacy Ollama/SwarmUI pair run much
faster with an NVIDIA GPU — see docs/GPU_SETUP.md for
the full walkthrough: host driver + nvidia-container-toolkit setup, the
ready-made docker-compose.gpu.yml override, TrueNAS SCALE GPU assignment,
model recommendations by VRAM (16 GB vs 32 GB V100), the fp16-everywhere /
non-flash-attention tuning set for the V100 profile, and the CUDA 13 /
driver-580 caveats specific to Volta cards (this same CUDA-13-drops-Volta risk
applies to the Unsloth image too — see finding I-7 in
docs/UNSLOTH_PHASE0_FINDINGS.md).
Still running the legacy SwarmUI backend? Its own first-run install
currently pulls a PyTorch build that cannot run on Volta at all — a device
reservation alone doesn't fix this. Run ./fix-swarmui-volta-torch.sh (repo
root) after SwarmUI's first start; see GPU_SETUP.md §3b for why and how to
check it's still needed after updates.
Ollama is defined in both docker-compose.yml and truenas-compose.yml behind the
ollama Compose profile — it only starts if that profile is active.
Downloading models:
The easiest way is via the in-app 🤖 Models tab (AI page):
- Click any chip in the Popular Models grid to start a download with live progress
- Or paste a custom model ID (e.g.
llama3.2:3b,hf.co/username/model) and click ⬇ Pull & Add - Downloaded models appear in the model list with a green status dot and a ✓ Use button to activate them for chat
From the command line:
# Docker Compose
docker compose exec ollama ollama pull gemma4:26b
# TrueNAS (find container name first)
docker ps | grep ollama
docker exec -it <name> ollama pull gemma4:26bRecommended models by hardware:
| Model | Size | Notes |
|---|---|---|
llama3.2:1b |
~0.9 GB | Minimum spec — works on anything |
llama3.2:3b |
~2 GB | Good quality, fast on CPU |
gemma3:4b |
~3 GB | Strong reasoning, low RAM |
gemma3:12b |
~8 GB | Best quality under 16 GB RAM |
gemma4:26b |
~17 GB | Best quality — needs 20+ GB RAM or GPU |
GPU acceleration:
Uncomment the deploy block in the Ollama service to enable NVIDIA GPU offloading:
ollama:
# ...
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]AMD GPU support requires the ROCm image: change image: ollama/ollama to image: ollama/ollama:rocm.
AI Chat and the "Ask AI" panel normally answer from the world's own web-edited Entities/Rules text (keyword search — no setup needed, this is always on). A GM who also keeps a separate Obsidian vault of freeform notes can additionally wire that vault in as a parallel knowledge source — it never replaces or requires touching the normal Entity-editing workflow:
- Settings → (world edit page): set Obsidian vault path to a folder this
server can read (a bind-mounted path for Docker) containing
.mdnotes. Notes can use[[wikilinks]]and simple YAML frontmatter (kind:,tags:, and any relation field whose value is a[[wikilink]], e.g.located_in: "[[Dockside]]"). - Pull an embedding model via Ollama —
ollama pull nomic-embed-text(the built-in default; a different embedding-capable model can be set instance-wide viaapp.ai.set_embed_model(), though there's no dedicated settings-page field for it yet). - Click 🔄 Rebuild indexes now on that same settings page whenever the vault
changes — a GM action, not automatic — to (re)build two indexes purely
derived from the vault (never hand-edited, always safe to rebuild):
- a semantic vector index (Ollama embeddings, brute-force cosine search) so a question phrased differently from the vault's own wording can still match, and
- a knowledge graph of typed entity relationships (from wikilinks/frontmatter, resolved by name against existing Entities) for multi-hop questions like "who does this NPC answer to, and who do THEY answer to".
A vault note whose title doesn't match any existing Entity by name still gets indexed for semantic search; it just doesn't contribute graph edges (auto-creating new Entities from unmatched notes may come later). Leave the vault path blank to disable this entirely — a world that never sets it behaves exactly as before.
SwarmUI is defined in all compose files behind the swarmui Compose profile — see
AI Setup above to enable it. On first boot it downloads the ComfyUI backend — this takes 5–15 minutes the first time.
Set SWARMUI_EXTERNAL_URL to your host's accessible URL to enable the Image Studio embedded iframe view.
The AI → Image Gen panel supports:
- Checkpoint model selection with deep subfolder scanning
- Size presets (512², 768×512, 1024², and more) plus custom W×H
- Sampler, scheduler, steps, CFG scale, seed
- Batch generation (1–4 images)
- LoRA name + weight
- VAE override, CLIP skip
- Upscaling — model selector (pulls from SwarmUI Upscale model folder) + ×1.5/×2/×3/×4 scale factor
- Image-to-image (img2img) — upload an init image and set denoising strength
- Generation history — last 50 generations stored in browser localStorage with one-click parameter reuse
- ⚡ Krea 2 GGUF Quick Setup — a one-click panel that downloads a matching Krea 2 Turbo GGUF diffusion model + Qwen3-VL-4B-Instruct text encoder pair, for running Krea 2 in far less VRAM than the full-precision checkpoint (see
docs/GPU_SETUP.md§5a for why this matters on a V100). One manual step remains outside nd-world's reach: runinstall-comfyui-gguf-krea2.sh(repo root) once on the machine where SwarmUI itself runs, to install the ComfyUI custom node Krea 2's GGUF files actually need.
See docs/GPU_SETUP.md for GPU passthrough (incl. sharing one V100 between Ollama and SwarmUI) and Volta-specific performance notes.
Audio is transcribed by Unsloth Studio's /v1/audio/transcriptions — there is no separate
transcription service to run. It turns an audio file attached to an AI Chat/Ask AI message into
text (so the chat model understands it whether or not it has native audio support), transcribes
session recordings (background jobs, one-shot upload and the live-recording panel), and writes
transcripts for audio/video library clips.
Download a Whisper model in Studio (Settings → Voice) — large-v3-turbo is a good default — then
pick it under ⚙️ Settings → System → Unsloth Studio server → Speech-to-text model and press
▷ Test STT. See docs/DEPLOYMENT.md for the
model-name caveats, tuning variables and what was removed with the old whisper.cpp sidecar.
Set IMAGEGEN_TYPE=comfyui and IMAGEGEN_URL to your ComfyUI instance address (default port 8188). A checkpoint must already be loaded before sending a request.
All persistent data lives in the /data volume:
world.db— SQLite database with all worlds, entities, and relationshipsuploads/— uploaded images and AI-generated images (uploads/ai-images/)diagnostics/— watchdog evidence: stack dumps captured the moment the app wedges, plus its startup/shutdown journal (see Diagnostics watchdog below)
🎯 Tools → 📦 Export & Backup (GET /export) is the one page for all of
this — Full Backup, World Book, World JSON, and Import to World JSON, each
with a one-line explanation of when to use it, instead of guessing between
similarly-named buttons scattered across the nav bar and the Worlds page.
From the hub, download World JSON (single file, or split into separate files for external tooling) — a self-contained JSON export of that world's entities with images embedded as base64.
From the hub, use the Import World JSON box to upload a previously exported World JSON file back into the active world.
On the Import page (🎯 Tools → 📥 Import, separate from World JSON
import above), the 🖼 Bulk Image Import section lets you select many
portrait/art files at once — each one is matched to an entity in the current world
by filename (e.g. Gandalf.png → the entity named "Gandalf"; underscores/dashes and
case are ignored, so aragorn_king.png matches "Aragorn" too). Review the matches
(a "check match" flag means it wasn't an exact match) and adjust any via the dropdown
before importing — files left on "— Skip —" aren't uploaded.
The World JSON export above only covers one world's entities and lore — it drops
characters, combat, quests, parties, sessions, calendar, tables, notes and schematics.
For a full-fidelity backup, log in as the GM and download Full Backup from
the Export & Backup hub (GET /admin/backup.zip). It streams a zip containing:
world.db— a consistent snapshot of the whole database (built with SQLite'sVACUUM INTO, so it's safe to download while the app is running — no risk of capturing a half-written page the way copying the live file directly would be)uploads/— every uploaded and AI-generated imagemaps/— all battle-map JSONmanifest.json— app name, timestamp, and a row count per table, for sanity-checking a restore
For unattended backups, run scripts/backup.sh (reads GM_EMAIL/GM_PASSWORD from
.env, logs in, and downloads the zip), e.g. from cron:
0 3 * * * cd /path/to/nd-world && BACKUP_DIR=/mnt/backups/nd-world KEEP=14 bash scripts/backup.sh >> /var/log/nd-world-backup.log 2>&1BACKUP_DIR is where zips are written (default ./backups), KEEP is how many to
retain before older ones are pruned (default 14).
In-app (recommended): from the Export & Backup hub, use ♻ Restore from Full
Backup — upload a previously downloaded Full Backup zip and type the confirmation
text shown (the active world's name, since Full Backup spans every world on the
instance) to stage it. Staging only extracts and validates the zip; nothing is
applied yet, and the app keeps running normally. The next time the container
restarts, the staged backup is swapped in automatically — before the app opens its
first connection to the database — the current world.db is renamed aside first
(never deleted), and uploads//maps/ are merged (files from the backup overwrite
same-named files, nothing already on disk is deleted). Restart to actually apply a
staged restore:
docker compose restart nd-worldA banner on the Export & Backup hub shows when a restore is staged and waiting, with a button to cancel it before restarting if you change your mind.
Manual (fallback — use if you can't reach the app, e.g. it's already down):
- Stop the container:
docker compose down(ordocker stop nd-world). - Unzip the backup and replace the contents of the
/datavolume with it:Either way,# Docker named volume docker run --rm -v nd-world-data:/data -v "$(pwd)":/backup alpine sh -c \ "rm -rf /data/* && unzip -o /backup/nd-world-backup-*.zip -d /data" # TrueNAS / host bind mount rm -rf /mnt/DeadPool/apps/nd-world/* unzip -o nd-world-backup-*.zip -d /mnt/DeadPool/apps/nd-world
world.db,uploads/, andmaps/should end up directly under/data(not nested inside another folder) —manifest.jsoncan stay alongside them or be discarded, it's only for sanity-checking row counts. - Start the container back up:
docker compose up -d.
If you can't reach the app (e.g. it's already down), the old copy-the-volume approach still works but isn't a point-in-time-consistent snapshot if the app was running when you took it:
# Docker named volume
docker cp nd-world:/data ./backup-$(date +%Y%m%d)
# TrueNAS bind mount — just copy the directory
cp -r /mnt/DeadPool/apps/nd-world ./backup-$(date +%Y%m%d)The app freezes silently when its single event loop wedges (a starved DB
connection pool, a SQLite write-lock pileup) — unhandled exceptions leave
tracebacks, but a hang leaves nothing. An always-on watchdog
(app/diagnostics.py) closes that gap:
- If the event loop stalls past
ND_DIAG_STALL_SECONDS(default 15s), or the DB pool saturates, it writes a full dump — every thread's stack, the asyncio task snapshot, pool status — to/data/diagnostics/at the moment it's stuck, rate-limited byND_DIAG_DUMP_COOLDOWN_SECONDS. - Every startup/shutdown/stall/recovery is journaled to
/data/diagnostics/events.log, so after an outage you can tell a kill (startup with no shutdown before it) from a clean restart at a glance.
Read it all from ⚙ Settings → Diagnostics (GM-only): live watcher status,
the journal tail, and the captured dumps with one-click viewing — no shell
access to the container needed. ND_DIAG_DISABLED=1 turns the whole thing off;
see .env.example for every knob.
/characters/new is a guided, rules-driven Player Character creation wizard implementing the
Neon & Dragons Core Rules character creation procedure:
- Name — character/player name, portrait
- Race — pick from the Standard / Advanced / Exceptional tiers (Consumed by Yellow also picks a Base Race)
- Profession — one of the six N&D professions
- Stats — 20-point allocation across the 8 stats using one of the three Physical/Mental splits (Eldritch uses a single 16-point pool; Advent AI allocates 10 points across mental stats only, with physical stats locked at 0), with a live derived-stats preview (HP/Shock/CA/Speed/PP/MP)
- Feats — required Race, Profession, and Common feat picks plus Free Feat slot(s) (2 for Humans), auto-granted Edge-rank Race/Profession feats, Child of the Black Goat's 16 Ritual feats, and Psyonic's 4 initial Rank 0 psy powers
- Equipment — spend a 5000-credit starting budget, with Weapons/Armor restricted to creation-eligible rarities (Simple/Standard)
The wizard is backed by a bundled copy of the same race/profession/feat/equipment catalog
(app/game_data/*.json) used by the NeonDragonsApp Android app and NeonDragonsEditor
desktop tool (from the sibling UoY-Neon-Dragons rules repo), so characters created here use the
same content IDs as those two apps.
Every character sheet has an ⬇ Export .ndc button (GET /characters/{id}/export.ndc) that
downloads a .ndc file — the same interchange format used by NeonDragonsApp and NeonDragonsEditor
for their own character import/export. The file can be imported directly into:
- NeonDragonsApp (Android) — Character List → import, or via the app's file share/open intent
- NeonDragonsEditor (desktop) — Manage Characters → Import
No changes are required in either app — both already accept a bare JSON array of character objects in this schema (NeonDragonsApp's legacy decode path, NeonDragonsEditor's native multi-character format), so nd-world only needs to produce a matching file.
app/game_data/*.json is a point-in-time copy of NeonDragonsApp/app/src/main/assets/data/*.json
from the UoY-Neon-Dragons repo. When race/profession/feat/equipment content changes there
(after running extract_all_data.py), refresh the copy here:
cp /path/to/UoY-Neon-Dragons/NeonDragonsApp/app/src/main/assets/data/*.json app/game_data/The Neon & Dragons wizard above isn't the only option — nd-world can run a completely
different TTRPG system alongside (or instead of) N&D, using Sheet Templates
(world switcher → 🗒 Systems & Templates, or /characters/templates).
A template is one of two modes:
- Extends the N&D sheet (the original behavior) — every character still has the full N&D sheet (stats, HP, Shock, PP/MP, edges, cyberware, conditions); the template's fields are extra sections layered on top.
- Fully custom system — the N&D sheet isn't used at all. The character is whatever fields the template defines, rendered on its own page instead of the N&D wizard. Fields can be single values (number/text/text area) or a list field — a repeatable group of sub-fields, for things like a variable-length ability list, inventory, or session log.
A built-in Asterion template (a d10 dice-pool system of Gods and Mythborn — Spark Shield/Flesh/Ichor, Glory, Domain Reclamation) ships as a working example of a fully custom system. To create a character with it, or with any custom-mode template, open 🗒 Systems & Templates and click + Create Character on that template's card — the plain + New Character button on the Characters page always goes to the standard N&D wizard, so existing N&D worlds are unaffected.
To build your own homebrew system: + New Template, choose Fully custom
system, then add fields — pick List (repeatable group) as a field's type to
define a group of sub-fields (e.g. an "Abilities" list where each entry has a
Name, Tier, and Effect). GM-authored templates can be scoped to one world or left
global (available everywhere), same as N&D's extra-field templates always were.
Custom-system characters skip N&D-only features that don't apply to them (.ndc
export, the HP/Speed/CA card badges) since those are Neon & Dragons–specific.
The Rules page is per-world, not global — a world falls back to the bundled
N&D core rules until its GM sets something else. On a world's Edit page, click
📖 Edit Rules to paste in Markdown directly, or upload a JSON file shaped
{"rules_md": "...markdown..."} via the import box on that same page (handy
for reusing a ruleset across worlds/installs without re-pasting). docs/asterion_rules.md
/ docs/asterion_rules.json in this repo are the full Asterion rules in both
formats, ready to paste or upload for any world using the built-in Asterion
character system.
The Rules renderer also supports directive blocks and a statblock fence —
:::tip|note|warning|danger|lore|collapse|gm [title] … ::: themed callouts
(:::gm content is removed server-side for non-GM viewers),
```statblock cards with a copy button — and an optional
rules.json overlay: a per-world JSON file (same import box) that adds
icons, custom section titles, reordering, tab groups, and per-section
player visibility to the auto-generated Rules page, keyed by the section
slugs the TOC generates. Full schema, slug rules, and a worked example:
docs/RULES_OVERLAY.md; a step-by-step cheatsheet
also ships inside the app on the Rules edit page. The 20 MB
MAX_UPLOAD_BYTES default applies to these imports too (raise it via
Settings → System → Upload limits if your rules file is bigger).
Want to run a Neon & Dragons world with Asterion's simpler, player-invented-ability
engine instead of N&D's fixed feat catalog, while keeping N&D's actual races,
professions, origins, and major edges? See docs/nd_asterion_conversion.md — a
complete, standalone character-creation and play rulebook built on Asterion's
engine: N&D's own Stats and Health/Shock/PP/MP resource layer, a Signature-Stat
roll-easing mechanic per race/profession, and every special mechanic each of
N&D's 17 races, 6 professions, 5 origins, and 22 major edges grants converted
into Asterion abilities.
Character Sheet Templates (above) are for Player Characters specifically. Field Templates are the equivalent for lore Entities — Characters, Creatures, Locations, Organizations, or any other kind — letting you attach structured data on top of the usual name/summary/body, instead of hand-writing it into the body text.
Manage them at 🗒 Field Templates (top of any entity list page). A template has a Kind it applies to (one specific kind, e.g. Creature, or "Any kind" to use it anywhere) and a list of fields:
- Text / Number / Text Area — simple values
- Dropdown — a constrained set of options (e.g. Status: Alive/Dead/Missing/Unknown)
- List (repeatable group) — a variable-length group of sub-fields, e.g. a stat block's "Abilities" list (each entry has its own Name and Effect)
Two built-in templates ship out of the box:
- NPC Details (Characters) — Title, Age, Gender, Status (Alive/Dead/Missing/Unknown)
- Stat Block (any kind) — Attack Pool, Defense Pool, Health, Armor, Speed, and a repeatable Abilities list — usable on Characters, Creatures, or anything else that needs quick combat stats
On an entity's create/edit form, pick a template from the Template dropdown (filtered to templates that apply to the currently-selected Kind) and its fields appear immediately — no page reload, and switching templates doesn't lose anything else you've already typed. Filled-in fields show on the entity's detail page, grouped under the section headings the template defines.
There is no public signup. One GM account runs the whole deployment, bootstrapped
from the GM_EMAIL/GM_PASSWORD environment variables on first start (bash scripts/setup.sh prompts for these interactively and writes them to .env). Every
other account is a player, created by redeeming a GM-issued invite link — there's
no way to sign up otherwise.
As the GM, open a world's Edit page (world switcher → ⚙ Manage worlds → Edit) to:
- Create/revoke Invite Links (optionally time- or use-limited) and share the
/join/<code>URL with a player — opening it lets them create an account (or log in) and joins them to that world - View and remove Members, open 🔒 Notes next to any member for a
private note thread with that player (e.g. session hooks meant only for them —
visible to you and them, never to the rest of the party), or 🔑 Reset
password for a locked-out player/assistant — generates a one-time temporary
password shown right there for you to relay to them (Discord, in person, etc.)
since there's no email set up to send it automatically. If you (the GM) get
locked out yourself, see
GM_PASSWORD_RESETabove — there's no one else with admin rights over your account to reset it for you. - Toggle whether players can see each other's characters (party roster, read-only) for that world
- Grant Player World Access to read-only browse Maps, Calendar, Quests, Parties, and/or Random Tables — off by default (Maps is on by default, matching how it's always worked), and creating/editing/deleting content in any of these stays GM/GM-Assistant only regardless; a Random Tables player also gets to roll
As a player, once joined to a world you can:
- Browse its lore, minus whatever the GM has hidden from you — each entity's edit page has a Visibility control with three modes: Everyone (default), GM only (spoilers, secrets, unrevealed content), or Specific players, which hides the entity from the party except for a hand-picked list of players (handy for per-character secrets, faction intel only one PC has access to, etc.)
- See any notes the GM has attached to a visible entity and un-hidden — the entity's own visibility and each note's visibility are independent, so the GM can show you a location while still keeping a couple of secret notes on it GM-only
- Create and manage your character(s) — one by default, more if the GM raises the world's Characters per player — via the creation wizard,
including live HP/Shock/PP/MP tracking and
.ndcexport - View party members' characters read-only, if the GM has enabled that
- Read private notes the GM has written to you (🔒 My Notes in the nav)
GM-only tools (AI chat, image generation, world/entity editing, maps, schematics, investigation boards) stay restricted to the GM account regardless. Session logs and campaign-wide notes shared with players don't need a separate feature — use a Note entity (kind "note", subtype "session note"/"lore"/etc.) like any other piece of world content, with visibility set to Everyone or restricted to a subset of the party.
Going public: nd-world is private by default (every page requires login) — see docs/DEPLOYMENT.md for the full walkthrough of running it locally and then exposing it to the internet via a Cloudflare Tunnel (recommended — no router changes needed) so you can send invite links to players who aren't on your home network.
nd-world/
├── app/
│ ├── main.py # FastAPI routes
│ ├── models.py # SQLAlchemy ORM models
│ ├── database.py # DB init and migrations
│ ├── ai.py # Ollama + image gen integration
│ ├── constants.py # N&D default stats, skills, currency
│ ├── auth.py # Password hashing, session/permission dependencies
│ ├── game_catalog.py # Race/profession/feat/equipment catalog loader for the PC wizard
│ ├── game_data/ # Bundled races/professions/feats/equipment JSON (from UoY-Neon-Dragons)
│ ├── routers/
│ │ ├── ai.py # /api/ai/* endpoints
│ │ ├── auth.py # /login, /logout, /join/{code} (invite redemption)
│ │ └── characters.py # /api/characters/* endpoints, .ndc export
│ └── templates/ # Jinja2 HTML templates
│ ├── ai_chat.html # AI chat + Image Gen + Models tabs
│ ├── imagestudio.html # SwarmUI iframe page
│ ├── auth/ # Login, join/signup pages
│ └── characters/ # Character sheet, list, creation wizard, edit form
├── static/
│ └── style.css # All app styles
├── scripts/
│ └── setup.sh # One-command install: Docker check, .env + GM account, start stack
├── docs/
│ ├── DEPLOYMENT.md # Local setup + Cloudflare Tunnel walkthrough
│ ├── API_REFERENCE.md # Every HTTP route + MCP tool: method, path, auth tier, purpose
│ ├── wiki/ # Usage guide for GMs/players (Home.md is the index)
│ ├── AI_ENTITY_GUIDE.md # For AI agents: entities/templates/characters + the general importer
│ └── AI_SCHEMATIC_GUIDE.md # For AI agents: schematic elements + map overlays
├── AGENTS.md # Entry point for AI agents/assistants working with this repo
├── docker-compose.yml # Linux / Windows stack (named volumes)
├── truenas-compose.yml # TrueNAS SCALE stack (bind mounts)
├── .env.example # SECRET_KEY / GM_EMAIL / GM_PASSWORD / COOKIE_SECURE template
├── Dockerfile
├── requirements.txt
| Service | Port | Compose file |
|---|---|---|
| nd-world | 8080 | docker-compose.yml |
| nd-world | 8087 | truenas-compose.yml |
| SwarmUI | 7801 | both |
| Ollama | 11434 | both |
AI dot stays grey / chat returns errors
- Open AI → 🤖 Models — the Ollama status indicator shows whether the service is reachable and the URL it's connecting to
- Check the Ollama container is running:
docker compose ps - View Ollama logs:
docker compose logs ollama - If you changed
OLLAMA_URLto point at a host Ollama, make sure it's notlocalhost(which resolves inside the container) — use your LAN IP instead
No models in the Models tab
- Click ↺ Refresh or go to the Popular Models grid and click a chip to download one
- The model list is empty until at least one model is pulled
Image generation returns an error
- Check backend status at
/api/ai/imagegen/statusin your browser - SwarmUI first-run can take 10–15 minutes — check logs:
docker compose logs -f swarmui - Confirm at least one checkpoint model is present in the models directory
- The Image Gen model dropdown refresh button (↺) rescans the SwarmUI model folders
Image Studio iframe is blank or shows connection refused
SWARMUI_EXTERNAL_URLmust be a URL your browser can reach — not an internal Docker hostname likehttp://swarmui:7801- Use your LAN IP:
SWARMUI_EXTERNAL_URL=http://192.168.1.xxx:7801 - On Windows, ensure the Windows Firewall allows port 7801
Models or LoRAs not showing in Image Gen dropdowns
- The scanner uses
depth: 10for nested subfolders — make sure the files are inside the SwarmUI models directory - Click the ↺ refresh button next to the dropdown to force a rescan
Database locked / app won't start
- Only one nd-world instance should write to the same
world.db - Recover from a crashed container:
docker compose restart world
Port already in use on startup
- Change the host port in your compose file (left side of the mapping):
ports: - "8090:8000" # use 8090 instead of 8080
Uploads not persisting after restart
- Confirm the
/datavolume is mounted correctly; bothworld.dbanduploads/must be inside it - For TrueNAS: check the dataset path exists and the container has write permissions
SwarmUI models directory is empty after restart (TrueNAS)
- Make sure the models bind mount is correct in
truenas-compose.yml - The bind path must exist on the host before the container starts:
mkdir -p /mnt/DeadPool/apps/swarmui/models/Stable-Diffusion
This repository uses two separate licenses:
- Application code — MIT. Use it, fork it, self-host it, build on it, no restrictions.
- Game/lore content (
lore/, the core rules, Asterion's rules, the races/professions/feats/equipment catalog, world maps, and schematics) — CC BY-NC-ND 4.0. Free to read and use at your own table with attribution; no commercial use, no redistributing modified versions. This content is planned for future commercial TTRPG book releases for these settings.
See LICENSE and LICENSE-CONTENT.md for the exact scope and full terms.