Skip to content

Latest commit

 

History

119 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Layforge

A chat-based virtual TTRPG platform where an AI acts as Dungeon Master.

The system splits into a Master node — the only component holding LLM provider credentials and authoritative game state — and any number of thin Slave clients that render UI and carry a network connection, with no rules engine or provider credentials of their own. Everything mechanically consequential (dice, rules resolution, PvP, character validation, content policy) is a gate enforced in code at the tool-call layer, not something left to the model's discretion. See docs/design.md for the full design document — architecture, protocol, extensibility interfaces, and governance model.

layforge.org is the project's public site: a short pitch plus links to the Master redistributable/source on GitHub, and a live directory of currently-joinable self-hosted campaigns (see registry/README.md) — no central server your game depends on, purely opt-in discovery for a Host who wants their table listed.

Prerequisites

  • Go 1.24+ — the only hard requirement. Master (the only code so far) compiles to a single static binary with no other runtime dependency.
  • protoc + the Go protobuf/gRPC plugins, but only to generate master/internal/systemenginepb/: those files are gitignored, not checked into this repo, and imported unconditionally — a fresh clone will not build until you run protocol/generate.sh once (see Quick Start below; the script prints install commands for anything missing).
  • Everything else is optional, needed only for the feature it powers — see master/README.md for the full list (a System Engine sidecar, an Ollama server, a ComfyUI instance). No Node/npm/bundler is needed for either web client — both are hand-written HTML/CSS/JS with no build step.

Quick Start

git clone https://github.com/jamesplotts/layforge.git
cd layforge
./protocol/generate.sh   # one-time: generates master/internal/systemenginepb/
cd master
go run .

Then open http://localhost:8080/. See master/README.md for every flag — LLM/System-Engine/ ComfyUI endpoints, room passwords, the admin panel, and what each optional dependency unlocks.

If you use the admin panel's "Save & Restart" while still running via go run ., your terminal will look like the server died (a real, confirmed go run .-specific quirk — see master/README.md's own "go run . caveat") — it almost certainly didn't; check http://localhost:8080/ before assuming something broke.

Building a Real Binary

go run . is fine for a first look, but it recompiles on every invocation and its self-restart behavior is confusing (see the caveat just above) — build an actual binary instead for anything beyond a quick test:

cd master
go build -o master .
./master

This produces a single static executable (master/master) with no runtime dependency beyond what you explicitly configure (an Ollama server, a System Engine sidecar, etc. — all optional). It looks for its web/admin-web directories next to itself, so keep those two folders alongside the binary if you move it elsewhere:

cp -r master/master master/web master/admin-web /wherever/you/want/
cd /wherever/you/want/
./master

Cross-compiling for another platform is standard Go — no special flags this project needs beyond the usual GOOS/GOARCH:

GOOS=linux GOARCH=amd64 go build -o master-linux-amd64 .
GOOS=darwin GOARCH=arm64 go build -o master-darwin-arm64 .
GOOS=windows GOARCH=amd64 go build -o master-windows-amd64.exe .

For a real, always-on deployment (not just running it in a terminal), see registry/README.md for a working systemd-unit example — the same pattern applies to master, not just registry/.

Uninstalling

Master is a single static binary plus plain files on disk — nothing in this Quick Start touches a system-wide location, a package manager, or a registry, so removing it is just deleting what you created:

  1. Stop the running process (Ctrl+C, or sudo systemctl stop layforge-master/disable it if you set it up as a service — see below).
  2. Back up layforge.db first if you want to keep anything — every campaign, character sheet, and adventure event Master has ever recorded lives in that one SQLite file (design doc §10), along with the admin panel's own settings. It defaults to sitting inside the working directory you ran go run ./the compiled binary from, so the next step deletes it right along with everything else, with no separate confirmation and no recovering it afterward. Copy it somewhere first (cp layforge.db ~/layforge-backup.db) if that data matters to you, or open it with sqlite3 layforge.db to export specific tables.
  3. Delete the cloned directory (rm -rf layforge). This removes the binary, the generated master/internal/systemenginepb/ stubs, the web client assets, and layforge.db itself (per the warning just above). If you pointed -db, -web-dir, or -admin-web-dir at a path outside the clone, remove those separately too.

That's the whole self-hosted footprint — no admin-panel data, campaign state, or credentials live anywhere else.

Two things Quick Start installs that are not Layforge-specific and are shared with any other Go/protobuf project on your machine — leave these alone unless you're sure nothing else needs them:

  • protoc-gen-go/protoc-gen-go-grpc (go installed into $(go env GOPATH)/bin) — remove with rm "$(go env GOPATH)/bin/protoc-gen-go" "$(go env GOPATH)/bin/protoc-gen-go-grpc".
  • protobuf-compiler (installed via your OS package manager, e.g. sudo apt remove protobuf-compiler on Debian/Ubuntu).

If you deployed Master (or the registry/ service) as a real systemd unit — following the pattern documented in registry/README.md — uninstalling means reversing that setup on whatever machine runs it, same as for any systemd service:

sudo systemctl disable --now <unit-name>
sudo rm /etc/systemd/system/<unit-name>.service
sudo systemctl daemon-reload

then delete the deployed binary/web/ directory and, if you set up an Apache vhost fronting it, remove that vhost file and reload Apache too.

Model Providers

Master picks which LLM narrates and runs the DM tool-use loop via -llm-provider (or the admin panel's System tab, live) — one of:

Provider -llm-provider Needs
Ollama (self-hosted) ollama (default) -llm-url only, no API key
Anthropic (Claude) anthropic -llm-api-key
OpenAI (ChatGPT) openai -llm-api-key
OpenRouter openrouter -llm-api-key
Z.ai zai -llm-api-key

Every provider but Ollama talks to its own published default endpoint unless -llm-url overrides it (e.g. a self-hosted OpenAI-compatible gateway). -llm-model takes whatever model name/tag the chosen provider expects — an Ollama tag like qwen3.8:27b, or a vendor model id like claude-opus-5/gpt-5/glm-4.6. Credentials never leave Master: no Slave client ever receives an API key, and the admin panel that sets one is itself a local-only listener (design doc §3.3). See master/README.md for the full flag reference.

Status

Multiple players can connect to the same campaign, chat, and get real AI-narrated responses: the system.connect handshake, then Master routes messages between everyone connected — safety.flag broadcasts to the whole table (design doc §9.2), and narrative.player_input renders through an LLM into narrative.player_bubble (§7's fast pass only, no DM/NPC reaction pass yet). Everything exchanged is durably logged to SQLite, with bidirectional history paging — join and see what's recent, page back further for scrollback. The V1 web client is served by Master itself by default, straight from disk so a table can restyle it without touching Go or rebuilding anything (see master/web/README.md). Master can now dial a real OpenCombatEngine gRPC sidecar (-system-engine-addr) and calls it for real: uploading a character (character.upload) gets mechanically parsed and validated by the engine and persisted, with the engine's warnings sent back (character.validation_result, design doc §9.4's mechanical half), and a player can roll an authoritative check for a character they own (roll.check_request), with the real outcome — including individual dice, not just a total — broadcast to the whole table as roll.request/roll.result, animated on a real WebGL d20 (three.js + cannon-es physics) with a swappable community-skin system in the web client. A player can also read back their own character's current data and mechanical status, rendered as a read-only sheet generated directly from the system engine's own JSON Schema — no D&D-specific fields hardcoded into the UI — and apply a real effect (damage/heal) to it, persisted server-side and reflected back in the sheet. A DM/NPC reaction pass (§7's slow pass) now runs after the fast pass: an LLM narrates what happens next and can call real tools — resolve_check, apply_effect, get_character_status (design doc §8) — against the same System Engine RPCs already wired for players, with every call broadcast to the table as tool.result and the final reaction as narrative.dm_prose. The DM can also start real structured turn order (start_combat) — rolling initiative through the System Engine rather than trusting the model to order it — broadcast to everyone as turn.state, and now actually enforced on players too: once combat is active, a player's own roll or effect on their character is rejected unless it's currently that character's turn (advance_turn, design doc §9.3). An unconscious/dying character isn't skipped — they get a turn that automatically rolls a death saving throw instead (a real System Engine RPC, StartTurn, newly added so OpenCombatEngine's own SRD death-save logic — previously built but unreachable — is actually wired up), broadcast as a genuine roll just like any other. The DM can also give a narrated monster/NPC a real mechanical presence on the fly — create_npc (after get_character_schema so the model authors a document that actually matches the campaign's schema, never a guessed shape) persists it the same way a player's own character upload does, so it can then be referenced by resolve_check, apply_effect, or start_combat like any other character. A campaign can now also configure a real PvP policy — pve_only/pvp_allowed — that mechanically gates whether the DM can damage one player's character on another's behalf, and a maturity-tier text constraint injected into DM narration, both via a per-campaign JSON config file (-campaign-policies); an unconfigured campaign gets the strictest PvP setting by default, not an open one. Turns are now mechanically tied to real rules too: landing a turn on any non-dead character automatically rolls a death saving throw for one that's unconscious/dying (a real System Engine RPC, StartTurn) — SRD's own "deterministic bookkeeping, not something the DM has to remember," now actually wired up rather than sitting unreachable in the engine. The DM can also illustrate a scene — generate_scene_image calls a pluggable image-gen provider (a self-hosted ComfyUI instance is the reference implementation) and broadcasts the result to the table, now verified live against a real running ComfyUI instance. A local-only admin/operator settings panel now exists too — a second, 127.0.0.1-only listener serving a tabbed web UI (Campaign/Security/System) for changing PvP policy, maturity-tier prompts, and room passwords live, or process-level settings (LLM/System-Engine/ComfyUI endpoints, listen address) via a self-triggered graceful restart — that restart now genuinely applies a saved System-tab change (a real gap fixed this pass: the settings database wasn't actually re-read at boot before). Master can also opt individual campaigns into a public directory at layforge.org (registry/, a separate standalone service) — a Host explicitly lists a campaign's adventure name, level range, and player count; nothing is published without that per-campaign opt-in, and a listing self-heals/expires on its own if Master stops checking in. Narration and the DM tool-use loop can now also talk to a hosted LLM provider — Anthropic, OpenAI, OpenRouter, or Z.ai — instead of only a self-hosted Ollama server (see Model Providers above); credentials for whichever one is configured never leave Master. Master now also gates on a Host/operator terms acceptance (once, via the admin panel or a scripted -accept-terms-version flag) and a per-connection player disclaimer acceptance before processing any real message — see master/README.md's Running section. The admin panel can also generate a full campaign pack from a one-paragraph description via the configured LLM, reviewed and editable before anything is written to disk — live-verified against a real local model, which also surfaced and fixed four real reliability issues (see master/README.md's Status section). A player's own roll still doesn't apply its own damage outside the DM pass, there's no human review step on imported characters (that needs an account/operator concept the admin panel doesn't cover yet), and no full markdown campaign-pack directory tree (§6.4) — policy configuration today is a flat JSON file, not campaign.md front matter. See master/README.md for the full picture, including exact roadmap gaps.

Layout

master/            Master process (Go) — session orchestration, turn-order
                    state machine, authoritative dice, tool-use dispatch,
                    and (master/web/) the V1 chat/player client it serves
registry/          Standalone Go service behind layforge.org — the public
                    campaign directory and project homepage
protocol/          AsyncAPI spec for the client-facing WebSocket protocol,
                    plus the System Engine gRPC/protobuf contract
campaign-packs/    Directory-based campaign content (markdown + YAML) —
                    sable-ravine/ is a full worked example, TEMPLATE/ an
                    annotated skeleton to copy
maturity-tiers/    Content-maturity tier definitions
docs/              Design document and supporting docs, including
                    authoring-campaign-packs.md

To write your own adventure, copy campaign-packs/TEMPLATE/ and follow docs/authoring-campaign-packs.md. Ready-made packs are published at layforge.org and can be pulled straight onto a Master from the admin panel's Campaign tab.

Related repos

  • jamesplotts/opencombatengine — the D&D SRD-compatible system-engine reference implementation this harness plugs into over gRPC (see §6.1 and §12 of the design doc).

License

MIT for code. Game-mechanics content generated by or shipped with this project is intended to stay SRD-legal (see §6.4 and §12 of the design doc) — it does not reproduce proprietary published material.

Contact

About

AI DM Harness — chat-based virtual TTRPG platform, Master/Slave architecture

Resources

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages