Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

config.toml

General client settings: connection, UI behavior, sound, TTS, and the web server. Most values can also be changed in-app via .settings. Apply file edits with .reload settings.

Connection

[connection]
host = "127.0.0.1"
port = 8000
character = "YourName"

# For direct connection (optional - can use CLI instead)
account = "your_account"
password = "your_password"  # Stored in plain text!
game = "prime"              # GS4: prime, platinum, shattered, test
                            # DR: dr, drplatinum, drfallen, drtest

Tip: For security, omit password — VellumFE prompts for it securely at startup in direct mode. CLI arguments override these values.

User Interface

[ui]
buffer_size = 10000             # Default lines kept per window
border_style = "single"         # single, double, rounded, thick, none
countdown_icon = "█"            # Glyph for RT/CT timer blocks
color_mode = "direct"           # direct, slot, indexed (see below)

# Text selection
selection_enabled = true
selection_respect_window_boundaries = true
selection_auto_copy = true      # Copy on mouse-up

# Commands
command_echo = true             # Show sent commands in main window
min_command_length = 3          # Min length to save in history

# Drag modifier for moving windows
drag_modifier_key = "ctrl"      # ctrl, alt, or shift

Every game window (dialogs, panels, streams, containers) is hidden by default and never auto-opens until you show it in the Windows list (Windows → Show/Hide windows). There is no dialog blocklist — hidden windows simply stay hidden even when the game re-sends them.

Color Modes

ModeDescription
direct24-bit true color. Use with modern terminals (kitty, alacritty, Windows Terminal)
slot256-color with custom palette via .setpalette. For terminals supporting OSC 4
indexed256-color with standard palette (closest match). Safe fallback

Focus Navigation

Control which windows are focusable with Tab:

[ui.focus]
types = ["text", "tabbedtext"]  # Widget types that can receive focus
exclude = ["bounty", "society"] # Specific windows to skip
order = []                      # Custom focus order (empty = layout order)

Target List

Configure the targets widget display:

[target_list]
status_position = "end"         # "end" or "start"
truncation_mode = "noun"        # "full" or "noun"
excluded_nouns = ["arm", "coal"]
boss_color = "#ff5555"          # AscensionBoss / MiniBoss creatures
challenging_color = "#ffaa55"   # "challenging" creatures

[target_list.status_abbrev]
stunned = "stu"
frozen = "frz"
dead = "ded"

The boss and challenging colors apply when the game’s structured creature status feed classifies a creature; see Targets.

Highlights

Global toggles for the highlight system:

[highlights]
sounds_enabled = true           # Play sounds on match
replace_enabled = true          # Apply text replacements
redirect_enabled = true         # Route lines to other windows
coloring_enabled = true         # Apply color highlighting

Map

Where the map widget finds its data (GUI):

[map]
lich_dir = "C:/Lich5"              # Lich install (folder containing data/)
# mapdb_path = "C:/maps/map.json"    # explicit file; overrides everything
mapdb_repo = "Nisugi/mapdb"        # GitHub repo for Settings > Map downloads
mapping_mode = false               # Cartography mode: sketch unmapped rooms

All of these are editable in Settings → Map in the GUI. Downloaded map data outranks the Lich folder; an empty mapdb_repo disables downloads. mapping_mode gates ghost-room sketches — see ghost rooms.

Travel (.go2)

Managed by the client — you normally never edit this section:

[go2]
native_map_clicks = true   # Map clicks travel natively; false sends ;go2 to Lich

[go2.saved]                # .go2 save <name> writes these
bank = 3517

[go2.pathcodes]            # Maze routes, captured automatically in-game

native_map_clicks is also in Settings → Travel in the GUI. See the Travel chapter for the .go2 command family.

Sound

[sound]
enabled = true
volume = 0.7                    # 0.0 to 1.0
cooldown_ms = 500               # Min time between repeated sounds
startup_music = true
startup_music_delay_ms = 0      # Delay before the login theme starts

Text-to-Speech

Prefer the editors: Settings > Speech in the GUI, or the .tts commands on any frontend.

[tts]
enabled = false
rate = 1.0                      # 0.5 (slow) to 3.0 (engine max)
volume = 1.0
speak_thoughts = true
speak_speech = true
speak_main = false              # Usually too noisy
# voice = "Microsoft Zira"      # Voice by name (.tts voices lists them)

# Lines matching these regexes are shown but never spoken
# gags = ["^You feel fully rested"]

# Pronunciation fixes applied before speaking
# substitutions = [{ pattern = "Wehnimer's", replacement = "Wenimers" }]

The three speak_* toggles are classic shortcuts for main/thoughts/speech. Any other window can opt in per-window: the “speak new lines” checkbox in its right-click menu (TUI: window editor; tts_speak in layout.toml).

TTS queue navigation keys are bound in keybinds.toml (defaults: Ctrl+Alt+arrows, F7F11).

Web Server (Mobile Frontend)

Embedded HTTP + WebSocket server that lets a phone browser join the session. Off by default; see Mobile Web.

[web]
enabled = false
port = 8040           # base port; instances walk upward unless pinned
bind = "127.0.0.1"    # set "0.0.0.0" to allow phones on your LAN
pinned = false        # true = bind exactly this port or fail loudly

Phones must pair once via a token — run .webinfo in-game for the URL and QR code.

Security: pairing keeps strangers out, but keep the port on a trusted LAN; for off-LAN play use Tailscale/WireGuard. Never expose it to the open internet.

Quickbars

Define custom quickbar windows that send commands:

[quickbars]
default = "quick-custom"

[[quickbars.custom]]
id = "quick-custom"     # must be "quick" or start with "quick-"
title = "Custom"
entries = [
  { type = "link", label = "look", command = "look" },
  { type = "sep" },
  { type = "link", label = "inventory", command = "inventory" }
]

Stream Routing

Controls where a stream’s text goes when no window subscribes to it. Prefer the editor: .streams (or the GUI’s Streams panel) lists every known stream and lets you set its route.

[streams]
fallback = "main"               # Streams with no route entry go here
room_in_main = true             # Show room text in main (DR only)

[streams.routes]
# Per-stream policy: "discard", "main", or "window:<name>"
speech = "discard"              # Already echoed in main; drop the duplicate
targetlist = "discard"          # Lich script noise without a widget
logons = "window:arrivals"      # Send to a window (received even while hidden)

Windows that subscribe to a stream always win; routes only decide what happens to orphaned streams. A window:<name> route never creates or opens the window — if it doesn’t exist, the stream uses fallback.

Legacy: older configs used drop_unsubscribed = [...]. It still loads — each entry is migrated to routes.<stream> = "discard" — but routes is the current mechanism.

Logging

Capture raw XML for debugging (written to profiles/<character>/logs/):

[logging]
enabled = false
# dir = "logs"
# timestamps = true
# max_lines_per_file = 30000

Event Patterns

Regex patterns that drive countdown timers. The defaults cover standard stun messages; add your own:

[event_patterns.stun_rounds]
pattern = '^\s*You are stunned for ([0-9]+) rounds?'
event_type = "stun"             # which countdown this feeds (see below)
action = "set"                  # set or clear
duration_capture = 1            # capture group holding the duration
duration_multiplier = 5.0       # rounds -> seconds
enabled = true

The event_type names the countdown the pattern feeds: stun drives the stun window, rt/ct drive roundtime/casttime, and any other string feeds the countdown window whose id matches it (case-sensitive). To build a timer for a spell that lasts multiple rounds: add a custom countdown window (Windows > Add Window > Countdowns > Custom) and give it an id like tremors, then add a pair of patterns:

[event_patterns.tremors_start]
pattern = 'The ground begins to shake violently for ([0-9]+) rounds'
event_type = "tremors"
action = "set"
duration_capture = 1
duration_multiplier = 5.0       # rounds -> seconds
enabled = true

[event_patterns.tremors_end]
pattern = 'The ground grows still\.'
event_type = "tremors"
action = "clear"
duration = 0
enabled = true

Several patterns may share one event_type (the defaults ship four for stun). If the message carries no number, drop duration_capture and set a fixed duration in seconds instead. Patterns match script output too, so a Lich script can drive a countdown by echoing a line the pattern recognizes.

Script-driven timers: <vellumTimer>

Lich scripts can skip the regex entirely and feed a countdown directly by sending a tag to the client:

<vellumTimer id='dark-cataclyst' value='1764904999'/>

id is the countdown window’s feed id (same matching as event_type above, case-sensitive) and value is the absolute epoch end time in seconds — compute it in the script as Time.now.to_i + duration, the same convention <roundTime> uses, so it stays correct under lag. value='0' clears the timer. The tag never renders as text. From a Lich script:

puts "<vellumTimer id='dark-cataclyst' value='#{Time.now.to_i + 90}'/>"

Script-driven images: <vellumImg>

Lich scripts can float a real image into a text window, with the text wrapping around it:

_respond "<compDef id='sprite' src='sunset' rows='4' align='left'/>"

Art lives in ~/.vellum-fe/global/images/inline/. See Inline Images for the full tag reference, room-window examples, and supported formats.