Daemons are background services that provide global functionality to the MUD. They run as singleton objects and handle cross-cutting concerns like login, channels, and help systems.
mudlib/daemons/
├── login.ts # Player authentication and session management
├── channels.ts # Communication channels
├── help.ts # Help system
├── admin.ts # Administration functions
├── config.ts # Mud-wide configuration settings
├── combat.ts # Combat system management
├── quest.ts # Quest system management
├── guild.ts # Multi-guild system with skills and levels
├── lore.ts # World lore registry for AI integration
├── discord.ts # Discord channel bridge
├── area.ts # Draft area builder with grid layout
├── time.ts # In-game day/night cycle
├── reset.ts # Periodic room resets and NPC respawns
├── map.ts # Map/minimap generation
├── loot.ts # Random loot generation with quality tiers
├── race.ts # Race definitions and latent abilities
├── profession.ts # Profession/crafting skill tracking
├── gathering.ts # Resource gathering nodes
├── party.ts # Party grouping and auto-assist
├── pet.ts # Pet summoning and management
├── mercenary.ts # Mercenary hiring system
├── portrait.ts # AI character portrait generation with caching
├── tutorial.ts # New player tutorial progression
├── bots.ts # Simulated player bots
├── behavior.ts # NPC behavior script management
├── aggro.ts # NPC grudge/threat memory
├── announcement.ts # System announcements
├── snoop.ts # Admin snooping utility
├── prompts.ts # AI prompt template management
├── soul.ts # Emote/social action system
├── vehicle.ts # Vehicle system
├── intermud.ts # Intermud 3 protocol (TCP)
├── intermud2.ts # Intermud 2 protocol (UDP)
└── grapevine.ts # Grapevine cross-MUD network (WebSocket)
The login daemon handles player authentication, character creation, and session management.
/mudlib/daemons/login.ts
- Prompt for character name
- Authenticate returning players
- Guide new player creation
- Session reconnection after disconnect
- Player data persistence
Connection
│
▼
┌───────────────┐
│ Ask for name │
└───────────────┘
│
▼
┌───────────────┐ Yes ┌─────────────────┐
│ Player exists?│────────────▶│ Verify password │
└───────────────┘ └─────────────────┘
│ No │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Create new │ │ Check session │
│ character │ │ (reconnect?) │
└───────────────┘ └─────────────────┘
│ │
▼ ▼
┌───────────────┐ ┌─────────────────┐
│ Enter game │◀────────────│ Resume/New │
└───────────────┘ └─────────────────┘
When a player disconnects unexpectedly (not via quit):
- Room is notified with a fade message: "Player's form flickers and slowly fades from view..."
- Player is moved to the void (
/areas/void/void) - A disconnect timer starts (configurable via ConfigDaemon, default 15 minutes)
- Player remains in active players list (can be seen in
who)
When a player reconnects:
- Login daemon checks for existing active player
- If found, cancels any disconnect timer
- Transfers the new connection to existing player object
- Restores player to their previous location
- Room is notified: "Player shimmers back into existence!"
- No duplicate player is created
If the disconnect timer expires before reconnection:
- Player is automatically saved
- Player is removed from active players
- Server broadcasts a notification of the disconnection
The login daemon is typically invoked by the master object:
// Called by master when a new connection arrives
const loginDaemon = efuns.loadObject('/daemons/login');
loginDaemon.handleNewConnection(connection);The channels daemon manages global communication channels.
/mudlib/daemons/channels.ts
| Channel | Access | Description |
|---|---|---|
ooc |
All players | Out-of-character chat |
shout |
All players | Server-wide announcements |
builder |
Builders+ | Builder discussion |
admin |
Admins | Admin communication |
discord |
All players | Discord bridge (when enabled) |
| Prefix | Function | Example |
|---|---|---|
: |
Channel emote | ooc :laughs |
; |
Share GIF | ooc ;funny cats |
The ; prefix triggers a Giphy search and shares the result to all channel members. See Giphy Integration for details.
import { getChannelDaemon } from '../daemons/channels.js';
const channelDaemon = getChannelDaemon();
// Send a message
channelDaemon.sendMessage('ooc', player, 'Hello everyone!');
// Subscribe to a channel
channelDaemon.subscribe('ooc', player);
// Unsubscribe from a channel
channelDaemon.unsubscribe('ooc', player);
// Check if subscribed
const isSubscribed = channelDaemon.isSubscribed('ooc', player);
// Get channel subscribers
const subscribers = channelDaemon.getSubscribers('ooc');
// List available channels
const channels = channelDaemon.getChannels();Messages are formatted as:
[OOC] Hero: Hello everyone!
[Shout] Hero: The dragon is dead!
[Builder] Hero: How do I create a door?
[Admin] Hero: Server restart in 5 minutes
The daemon automatically checks permissions:
- Players can only access channels they're allowed to use
- Builder channel requires Builder+ permission
- Admin channel requires Administrator permission
The help daemon provides an in-game help system.
/mudlib/daemons/help.ts
Help topics are organized in categories:
mudlib/help/
├── index.ts # Help index and search
├── player/
│ └── basics.ts # Basic gameplay help
├── builder/
│ └── building-basics.ts
├── admin/
│ └── admin-commands.ts
└── classes/
├── fighter/
│ └── skills.ts
└── thief/
└── skills.ts
import { getHelpDaemon } from '../daemons/help.js';
const helpDaemon = getHelpDaemon();
// Get help index
const index = helpDaemon.getIndex();
// Get a specific topic
const topic = helpDaemon.getTopic('basics');
// Search topics
const results = helpDaemon.search('combat');
// List categories
const categories = helpDaemon.getCategories();
// Get topics in a category
const topics = helpDaemon.getTopicsInCategory('player');Help topics are TypeScript files that export content:
// /mudlib/help/player/movement.ts
export const title = 'Movement';
export const category = 'player';
export const keywords = ['move', 'walk', 'go', 'directions'];
export const content = `
# Movement
Use direction commands to move around the world.
## Basic Directions
- north (n) - Go north
- south (s) - Go south
- east (e) - Go east
- west (w) - Go west
## Special Exits
Some rooms have named exits like "enter tavern" or "climb ladder".
`;The admin daemon provides administrative functions.
/mudlib/daemons/admin.ts
- Permission management
- Player administration
- System commands
import { getAdminDaemon } from '../daemons/admin.js';
const adminDaemon = getAdminDaemon();
// Grant permission level
adminDaemon.grantPermission(player, targetPlayer, 'builder');
// Revoke permissions
adminDaemon.revokePermission(player, targetPlayer);
// Add domain access
adminDaemon.addDomain(player, targetPlayer, '/areas/castle/');
// Remove domain access
adminDaemon.removeDomain(player, targetPlayer, '/areas/castle/');
// Get audit log
const log = adminDaemon.getAuditLog(20);The config daemon manages mud-wide configuration settings that persist across server restarts.
/mudlib/daemons/config.ts
- Centralized configuration for game-wide settings
- Type-safe settings with validation (number, string, boolean)
- Constraints support (min/max for numbers)
- Automatic persistence to
/data/config/settings.json - Accessible via efuns from any mudlib code
| Setting | Type | Default | Description |
|---|---|---|---|
disconnect.timeoutMinutes |
number | 15 | Minutes before a disconnected player is force-quit |
combat.playerKilling |
boolean | false | Allow PvP combat |
corpse.playerDecayMinutes |
number | 60 | Minutes before player corpses decay (0 = never) |
corpse.npcDecayMinutes |
number | 5 | Minutes before NPC corpses decay |
reset.intervalMinutes |
number | 15 | Minutes between room resets |
reset.cleanupDroppedItems |
boolean | true | Clean up non-player items during room reset |
game.theme |
string | fantasy | Game theme/genre for AI-generated content |
Use config in-game with no arguments to see the full list of 35+ settings.
// Via efuns (recommended for mudlib code)
const timeout = efuns.getMudConfig<number>('disconnect.timeoutMinutes');
efuns.setMudConfig('disconnect.timeoutMinutes', 30);
// Direct daemon access
const configDaemon = efuns.findObject('/daemons/config');
// Get a setting value
const value = configDaemon.get<number>('disconnect.timeoutMinutes');
// Set a setting value (validates type and constraints)
const result = configDaemon.set('disconnect.timeoutMinutes', 30);
if (!result.success) {
console.log(result.error);
}
// Get all settings with metadata
const allSettings = configDaemon.getAll();
// Reset a setting to default
configDaemon.reset('disconnect.timeoutMinutes');
// Check if a setting exists
const exists = configDaemon.has('disconnect.timeoutMinutes');
// Get setting metadata (without value)
const info = configDaemon.getSettingInfo('disconnect.timeoutMinutes');
// { description, type, min?, max? }To add a new setting, update the DEFAULT_SETTINGS in /mudlib/daemons/config.ts:
const DEFAULT_SETTINGS: Record<string, ConfigSetting> = {
'disconnect.timeoutMinutes': {
value: 15,
description: 'Minutes before disconnected player is force-quit',
type: 'number',
min: 1,
max: 60,
},
// Add new settings here
'game.maxPlayersPerRoom': {
value: 50,
description: 'Maximum players allowed in a single room',
type: 'number',
min: 1,
max: 1000,
},
};Administrators can manage settings in-game using the config command:
config # List all settings
config disconnect.timeoutMinutes # View a setting
config disconnect.timeoutMinutes 30 # Change a setting
config reset disconnect.timeoutMinutes # Reset to default
The lore daemon manages world lore entries that can be injected into AI prompts for consistent NPC dialogue and content generation.
/mudlib/daemons/lore.ts
- Central registry for world lore, history, factions, and other background information
- Provides consistent context for AI-powered NPCs and content generation
- Supports categorization, tagging, and priority-based retrieval
- Persists lore entries to
/data/lore/entries.json
| Category | Description |
|---|---|
world |
General world facts, cosmology, creation myths |
region |
Geographic areas, kingdoms, territories |
faction |
Organizations, guilds, groups |
history |
Past eras, timelines |
character |
Notable NPCs, heroes, villains |
event |
Major historical events, wars, disasters |
item |
Artifacts, magical items, item types |
creature |
Monster types, beasts, supernatural beings |
location |
Specific notable places (buildings, dungeons) |
economics |
Trade, currency, commerce |
mechanics |
World mechanics (magic systems, etc.) |
faith |
Religions, gods, worship |
interface LoreEntry {
id: string; // Format: "category:slug" (e.g., "faith:sun-god")
category: LoreCategory;
title: string; // Display title
content: string; // AI-ready lore text
tags?: string[]; // For filtering (e.g., ["magic", "elves"])
relatedLore?: string[]; // IDs of related entries
priority?: number; // Higher = included first when truncating (default: 5)
}import { getLoreDaemon } from '../daemons/lore.js';
const loreDaemon = getLoreDaemon();
// Register a new lore entry
loreDaemon.registerLore({
id: 'faction:thieves-guild',
category: 'faction',
title: 'The Shadow Hand',
content: 'The Shadow Hand is the largest thieves guild...',
tags: ['criminal', 'underground'],
priority: 5,
});
// Get a specific entry
const entry = loreDaemon.getLore('faction:thieves-guild');
// Get all entries in a category
const factions = loreDaemon.getLoreByCategory('faction');
// Get entries matching tags
const magicLore = loreDaemon.getLoreByTags(['magic']);
// Search entries by keyword
const results = loreDaemon.search('dragon');
// Get all lore entries
const allLore = loreDaemon.getAllLore();
// Build AI context from multiple entries (with max length)
const context = loreDaemon.buildContext(
['world:creation-myth', 'faction:thieves-guild'],
2000 // max characters
);
// Remove an entry
loreDaemon.removeLore('faction:thieves-guild');
// Get all unique tags
const tags = loreDaemon.getAllTags();
// Persist changes
await loreDaemon.save();NPCs reference lore via their knowledgeScope.worldLore array:
export class Bartender extends NPC {
constructor() {
super();
this.setAIContext({
name: 'Mira the Bartender',
personality: 'Friendly tavern owner who knows all the local gossip.',
knowledgeScope: {
topics: ['local news', 'drinks', 'rumors'],
worldLore: [
'region:valdoria',
'faction:thieves-guild',
'economics:trade-routes',
],
},
});
}
}When the NPC responds to players, the lore daemon automatically fetches these entries and includes them in the AI prompt.
The Discord daemon manages the two-way message bridge between Discord and in-game channels.
/mudlib/daemons/discord.ts
- Connect to Discord via bot token
- Bridge messages between Discord channel and in-game "discord" channel
- Handle configuration and connection state
- Route incoming Discord messages to channel daemon
import { getDiscordDaemon } from '../daemons/discord.js';
const daemon = getDiscordDaemon();
// Configure the connection
await daemon.configure('guildId', 'channelId');
// Enable and connect
const result = await daemon.enable();
if (result.success) {
console.log('Connected to Discord!');
}
// Send a message to Discord
await daemon.sendToDiscord('PlayerName', 'Hello Discord!');
// Check status
const status = daemon.getStatus();
console.log(status.connected); // true/false
console.log(status.state); // connection state
// Disable and disconnect
await daemon.disable();In-Game to Discord:
Player: "discord Hello!"
→ ChannelDaemon.send('discord', ...)
→ DiscordDaemon.sendToDiscord('PlayerName', 'Hello!')
→ efuns.discordSend(...)
→ Discord shows: **PlayerName**: Hello!
Discord to In-Game:
Discord user sends message
→ DiscordClient receives message
→ DiscordDaemon.receiveFromDiscord('Username', 'message')
→ ChannelDaemon.receiveDiscordMessage(...)
→ Players see: [Discord] Username: message
discordadmin status # Show status
discordadmin configure <guildId> <channelId> # Configure IDs
discordadmin enable # Connect
discordadmin disable # Disconnect
discordadmin test # Send test message
See Discord Integration for complete documentation.
Lore can be managed in-game using the lore command:
lore list [category] # List all lore entries
lore show <id> # Show a specific entry
lore add <category> <title> # Add new lore (opens IDE)
lore edit <id> # Edit existing lore (opens IDE)
lore remove <id> # Remove a lore entry
lore generate <category> <title> [theme] # AI-generate lore
lore search <keyword> # Search lore content
lore tags # List all tags
See Commands Reference for details.
The guild daemon manages the multi-guild system with skills, levels, XP, and passive modifiers.
/mudlib/daemons/guild.ts
- Guild membership and level tracking
- Skill definitions and advancement
- XP awards and level-up requirements
- Passive stat modifiers applied on login
- Guild-specific commands and abilities
See Guilds for full documentation.
The combat daemon orchestrates all combat in the game.
/mudlib/daemons/combat.ts
- Combat round scheduling (1-5s dynamic intervals)
- Hit/dodge/parry/riposte/block resolution
- Threat calculation and target selection
- Death handling and corpse creation
See Combat for full documentation.
The quest daemon tracks quest definitions, player progress, and reward distribution.
/mudlib/daemons/quest.ts
- Quest definitions with kill/fetch/explore/deliver/talk/escort/custom objectives
- Player quest state tracking
- Reward distribution (XP, quest points, gold, items, guild XP)
- Quest panel updates via protocol messages
See Quests for full documentation.
The time daemon manages the in-game day/night cycle with four phases: dawn, day, dusk, and night.
/mudlib/daemons/time.ts
- Game clock with configurable cycle duration (default 60 real minutes = 24 game hours)
- Phase transitions with light modifiers for outdoor rooms
- GAMETIME protocol messages to update client sky/clock panels
See Sky and Time Display for full documentation.
The portrait daemon generates and caches AI-created images for NPCs, players, and items.
/mudlib/daemons/portrait.ts
- AI image generation via Gemini
- Three-tier caching (memory, disk, HTTP)
- Fallback SVG silhouettes
- Object image support for weapons, armor, containers, items
See Portraits for full documentation.
The profession daemon tracks player crafting, gathering, and movement skill progression.
/mudlib/daemons/profession.ts
- Profession level and XP tracking
- Skill rank calculations
- Recipe availability based on skill level
- Tool and station requirements
See Professions for full documentation.
The gathering daemon manages resource nodes and the gathering skill check system.
/mudlib/daemons/gathering.ts
- Resource node definitions and placement
- Gather attempt resolution (skill checks, yields)
- XP awards for successful gathering
See Professions for related documentation.
The tutorial daemon manages new player tutorial progression and special engage content.
/mudlib/daemons/tutorial.ts
- Tutorial step tracking per player
- Special engage content for tutorial NPCs
- Progression gating and completion tracking
The bot daemon manages simulated player bots that populate the game world.
/mudlib/daemons/bots.ts
- Bot creation with AI-generated personalities
- Bot login/logout and lifecycle management
- Bot behavior (roaming, chatting, NPC interaction)
- Configurable maximum bot count
Managed via the botadmin admin command. See Commands for details.
The area daemon provides a draft area builder with grid layout and publish-to-files system.
/mudlib/daemons/area.ts
- Grid-based area layout editing
- Room, NPC, and item template generation
- Publishing draft areas to the file system
The reset daemon periodically resets rooms to their initial state.
/mudlib/daemons/reset.ts
- Periodic room resets (default 15 minute interval)
- Respawning missing items and NPCs
- Reset scheduling and tracking
The map daemon generates map and minimap data for client display.
/mudlib/daemons/map.ts
- Map data generation for explored areas
- Minimap rendering data
See Map Navigation for related documentation.
The loot daemon generates random loot drops for NPCs with quality tiers and enchantments.
/mudlib/daemons/loot.ts
- Random loot table resolution
- Quality tier assignment (common through legendary)
- Affix and enchantment generation
See Random Loot for full documentation.
The race daemon manages race definitions and racial abilities.
/mudlib/daemons/race.ts
- Race stat bonuses and penalties
- Latent racial abilities applied on login
- Race data for character creation
See Races for full documentation.
The party daemon manages player grouping and auto-assist combat.
/mudlib/daemons/party.ts
- Party creation, invitation, and membership
- Auto-assist in combat
- XP sharing between party members
See Party System for full documentation.
The pet daemon manages pet summoning, behavior, and persistence.
/mudlib/daemons/pet.ts
- Pet template definitions
- Pet summoning and dismissal
- Pet behavior and following
See Pets for full documentation.
The mercenary daemon manages hireable mercenary NPCs.
/mudlib/daemons/mercenary.ts
- Mercenary hiring and dismissal
- Mercenary combat AI
- Contract duration and cost
See Mercenaries for full documentation.
The behavior daemon manages NPC behavior scripts and combat AI.
/mudlib/daemons/behavior.ts
- Behavior script loading and execution
- Combat skill selection AI
- NPC behavior state management
The aggro daemon maintains NPC grudge and threat memory.
/mudlib/daemons/aggro.ts
- Persistent threat memory (24-hour expiry)
- NPC grudge tracking across sessions
- Threat-based aggression triggers
See Aggro & Threat for full documentation.
The announcement daemon manages system-wide announcements displayed on the login screen.
/mudlib/daemons/announcement.ts
- Announcement creation and storage
- Latest announcement display on launcher
- Announcement history
The snoop daemon provides admin snooping capability to monitor player activity.
/mudlib/daemons/snoop.ts
- Admin can monitor another player's input/output
- Snoop session management
The prompts daemon manages AI prompt templates used by all AI content generation features.
/mudlib/daemons/prompts.ts
- Wraps the driver's PromptManager for mudlib access
- Template rendering with
{{variable}}substitution and{{#if}}conditionals - Override management (custom templates replace defaults)
- Reload overrides from disk
import { getPromptsDaemon } from '../daemons/prompts.js';
const prompts = getPromptsDaemon();
// Render a template with variables
const text = prompts.render('describe.system', { styleGuide: 'brief' });
// Get a template by ID
const template = prompts.get('ainpc.system');
// Get all registered prompt IDs
const ids = prompts.getIds();
// Check if a prompt has a custom override
const overridden = prompts.hasOverride('describe.system');
// Set a custom override
await prompts.set('describe.system', 'New template: {{styleGuide}}');
// Reset to default
await prompts.reset('describe.system');
// Reload overrides from disk
await prompts.reload();All AI commands (aidescribe, airoom, ainpc, ailore, etc.) use prompts.render() instead of hardcoded prompt strings. The {{gameTheme}} variable is automatically injected from the game.theme config setting.
Managed via the prompts admin command. See Commands for details.
The soul daemon provides the emote and social action system.
/mudlib/daemons/soul.ts
- Emote definitions (targeted and untargeted)
- Social action resolution
- Emote listing and discovery
The vehicle daemon manages the vehicle system for player transportation.
/mudlib/daemons/vehicle.ts
- Vehicle definitions and behavior
- Boarding and disembarking
- Vehicle movement and routes
See Vehicles for full documentation.
The intermud daemon connects to the Intermud 3 network via TCP for cross-MUD communication.
/mudlib/daemons/intermud.ts
- I3 protocol implementation (TCP)
- Cross-MUD channel messaging
- MUD directory and who lists
See Intermud for full documentation.
The intermud2 daemon implements the older Intermud 2 protocol via UDP.
/mudlib/daemons/intermud2.ts
- I2 protocol implementation (UDP)
- Legacy cross-MUD communication
The grapevine daemon connects to the Grapevine cross-MUD network via WebSocket.
/mudlib/daemons/grapevine.ts
- Grapevine protocol implementation (WebSocket)
- Cross-MUD channel messaging
- Player presence sharing
// /mudlib/daemons/mydaemon.ts
import type { MudObject } from '../std/object.js';
let instance: MyDaemon | null = null;
export class MyDaemon {
private data: Map<string, unknown> = new Map();
constructor() {
// Initialize daemon state
}
// Your daemon methods
doSomething(player: MudObject): void {
// Implementation
}
}
// Singleton accessor
export function getMyDaemon(): MyDaemon {
if (!instance) {
instance = new MyDaemon();
}
return instance;
}// In a command or other mudlib code
import { getMyDaemon } from '../daemons/mydaemon.js';
const daemon = getMyDaemon();
daemon.doSomething(player);- Use Singleton Pattern - Daemons should be single instances
- Initialize Lazily - Create instance on first access
- Handle Persistence - Save/load state if needed
- Check Permissions - Verify caller has appropriate access
- Log Actions - Audit important operations
- Handle Errors - Don't crash on invalid input
Daemons are loaded on demand and persist for the lifetime of the driver:
Driver Start
│
▼
┌─────────────────┐
│ Daemon not │
│ loaded yet │
└─────────────────┘
│
▼ First access (getXxxDaemon())
┌─────────────────┐
│ Create daemon │
│ instance │
└─────────────────┘
│
▼
┌─────────────────┐
│ Daemon active │◀──┐
│ (singleton) │───┘ All subsequent calls
└─────────────────┘
│
▼ Driver shutdown
┌─────────────────┐
│ Daemon cleanup │
└─────────────────┘
class EventDaemon {
private listeners: Map<string, Set<MudObject>> = new Map();
subscribe(event: string, listener: MudObject): void {
if (!this.listeners.has(event)) {
this.listeners.set(event, new Set());
}
this.listeners.get(event)!.add(listener);
}
unsubscribe(event: string, listener: MudObject): void {
this.listeners.get(event)?.delete(listener);
}
emit(event: string, data: unknown): void {
const listeners = this.listeners.get(event);
if (listeners) {
for (const listener of listeners) {
// Call listener's handler
(listener as { onEvent?: (e: string, d: unknown) => void }).onEvent?.(event, data);
}
}
}
}class TimerDaemon {
private timers: Map<string, NodeJS.Timeout> = new Map();
schedule(id: string, callback: () => void, intervalMs: number): void {
this.cancel(id); // Cancel existing
this.timers.set(id, setInterval(callback, intervalMs));
}
cancel(id: string): void {
const timer = this.timers.get(id);
if (timer) {
clearInterval(timer);
this.timers.delete(id);
}
}
}Daemons use the data persistence efuns which route through the active persistence adapter (filesystem or Supabase):
class PersistentDaemon {
private data: Record<string, unknown> = {};
private namespace = 'mydaemon';
private key = 'state';
async load(): Promise<void> {
if (await efuns.dataExists(this.namespace, this.key)) {
const content = await efuns.loadData(this.namespace, this.key);
this.data = JSON.parse(content);
}
}
async save(): Promise<void> {
await efuns.saveData(this.namespace, this.key, JSON.stringify(this.data, null, 2));
}
}See Persistence Adapter for the full adapter pattern and namespace mapping.