- Go 100%
| cmd/slopgame | ||
| docs | ||
| internal | ||
| .gitignore | ||
| AGENTS.md | ||
| go.mod | ||
| go.sum | ||
| README.md | ||
Slopgame
Local-first LLM-backed TUI RPG foundation in Go.
Slopgame is a single-player RPG that runs entirely on your machine. The engine is written in Go, game state lives in an on-disk SQLite database, and any OpenAI-compatible local LLM endpoint drives scene narration. See docs/LLM_RPG_MVP_S1.md for the design notes that drove this milestone.
How to run
Prerequisites:
- Go 1.26 or newer
- An OpenAI-compatible HTTP LLM endpoint (the default points at
http://localhost:8080/v1)
Build and launch the TUI:
go run ./cmd/slopgame
If your checkout lacks full Git metadata and Go refuses to stamp the build, disable VCS stamping:
go run -buildvcs=false ./cmd/slopgame
When the TUI opens you walk through the session-creation wizard, which collects a title, LLM base URL / model, setting outline, tone, content rating, worldgen seed, and player profile.
Once a session is bootstrapped:
- Type free-text input and press Enter to submit a turn.
- Type
/followed by an admin command (see below) to bypass the LLM and apply deterministic state changes. - Press F1 at any time to see the keyboard help overlay.
- Press F2 to reopen the latest debug command result.
- Press Ctrl+Q to quit (asks for confirmation) or Ctrl+C to force-quit.
While a turn is in flight the input field is locked and the turn assist panel shows every pipeline stage with the current stage highlighted.
How to play
[TBW]
Admin commands
Slopgame exposes a closed set of slash commands that mutate game
state deterministically without going through the LLM. Type /help
in the TUI for the live list.
| Command | Description |
|---|---|
/help |
Print the command list. |
/debug state |
Dump the current state snapshot (characters, items, etc.). |
/debug context |
Show the context the LLM would see on the next turn. |
/debug last-events [n] |
Show the last n game events (default 10). |
/debug relationships [character-id-or-name] |
Per-pair view of every relationship: all 9 axes (trust, respect, affection, attraction, comfort, commitment, tension, fear, resentment), label, romance flags, and timestamps. Optional character filter narrows to one source. |
/inventory add <item-id> <qty> |
Add qty of item-id to the player inventory. |
/inventory remove <entry-id> <qty> |
Remove qty from inventory entry entry-id. |
/character set <character-id> mood <mood> |
Set a character's mood. |
/character hp <character-id> <delta> |
Adjust a character's HP by delta (negative for damage). |
/party add <character-id> |
Add a character to the active party. |
/party remove <character-id> |
Remove a character from the active party. |
/relationship <character-id> <axis> <delta> |
Adjust a relationship axis score by delta. |
/status add <character-id> <status-id> [dur] |
Apply a status effect, optionally with a duration. |
/status remove <character-id> <status-id> |
Remove a status effect. |
/event generate <description> |
Force a deterministic game event from a free-text prompt. |
/save |
Force-flush the session to disk. |
Identifiers: character-id, item-id, entry-id, and status-id
must be the canonical IDs (e.g. char_*, npc_*) currently present
in the session. Run /debug state to list them, or read them off the
*_id fields from /debug last-events.
Session storage
By default sessions live under
~/.local/share/slopgame/sessions/<session-id>/. Each session
directory contains:
session.yaml— the canonical YAML config (id, title, model, setting, worldgen seed, player seed).game.db— the SQLite database with characters, factions, locations, items, quests, lore facts, summaries, memory snapshots, game events, and the raw visible log.logs/— append-only log files:engine.log,worldgen.log,llm.log,admin.log— per-subsystem progress lines.worldgen/<NNN>_<stage>.{prompt.md,response.txt,parsed.json}— per-stage artifacts from the worldgen orchestrator.turns/<turn-id>/<subsystem>.{prompt.md,response.txt,parsed.json}— per-turn trace artifacts from the curator.
Configuration
Slopgame speaks the OpenAI-compatible HTTP API. Point the wizard at
any server that exposes /v1/models and /v1/chat/completions
(llama.cpp, vLLM, llama-swap in front of llama.cpp, an OpenAI
endpoint, etc.).
Environment variables
The wizard reads two optional environment variables so the operator does not have to retype the same endpoints on every session:
| Variable | Effect |
|---|---|
SLOPGAME_BASE_URL |
Pre-fills the base-URL field (default http://localhost:8080/v1). |
SLOPGAME_MODEL |
Pre-selects the model in the discovery list, or pre-fills it in the manual-entry step when the server does not advertise it. |
Both variables are still editable in the wizard; they only set the
starting values. Empty or whitespace-only values are ignored. They
also take precedence over values saved in session.yaml.
Starting a new session from an existing one
Pick New session at the top-level menu. When at least one session
already exists on disk, the wizard stops at a small chooser with two
options: Blank session and From existing session.... Pick
From existing session... to pre-populate every field from the
chosen source session's session.yaml; the worldgen seed is reset to
"blank → use current time" so the duplicate gets a fresh world, and
the title gets (copy) appended.
When no sessions exist yet, the chooser is skipped and you go straight from New session to a blank wizard. If the source session is removed between picking it and confirming, the TUI surfaces the failure and returns to the menu instead of launching a wizard with missing data.
The engine never blocks on the LLM longer than the configured request timeout. Failed LLM calls surface in the turn assist panel so the operator can retry; previously committed events are preserved.
How to test
The test suite uses fake LLM clients and temporary session
directories. Nothing in go test ./... hits the network.
go test ./...
The acceptance smoke test that proves the S1 pipeline runs end to end
lives at internal/app/smoke_test.go. It:
- Creates a session through
SessionCreationService. - Runs all eight worldgen stages with a scripted fake LLM.
- Reloads the session through
SessionLoadingService. - Submits one player turn through the turn
Orchestrator. - Runs the
Memory Curatorwith a fake LLM response so at least one lore fact and one summary are committed. - Reloads the session and confirms the raw log, lore facts, and summaries survive handle close/reopen.
Two supporting tests cover:
- LLM error surfacing (
TestSmoke_S1LLMErrorIsSurfaced). - Raw log persistence across reload
(
TestSmoke_S1ReloadPreservesRawLog).
To run only the smoke tests:
go test -run TestSmoke ./internal/app/
To re-run the full suite without the test cache:
go test -count=1 ./...
If Go complains about VCS stamping, repeat with -buildvcs=false.
