14 KiB
Pre-Commit Test Checklist
Automated smoke test (same as CI): run from the repo root:
./run-tests.sh
This builds the server and mudtool, starts the server with a temporary DB, and runs the same sequence as the smoke steps in .gitea/workflows/smoke-tests.yml (new player, persistence, mudtool admin, in-game admin, registration gate, tick combat), then cleans up. Use MUD_TEST_DB (default ./mudserver.db.test) so you do not overwrite your normal mudserver.db.
Prerequisites: Rust toolchain (cargo), OpenSSH client, and OpenBSD nc (netcat-openbsd on Debian/Ubuntu) for scripts/ci/wait-for-tcp.sh. CI installs these explicitly before the smoke steps.
Run through the checks below before every commit to ensure consistent feature coverage.
Build
cargo buildsucceeds with no errorscargo build --bin mudtoolsucceeds
Server Startup
- Server starts:
RUST_LOG=info ./target/debug/mudserver - World loads all rooms, NPCs, objects, races, classes (check log output)
- Database opens (or creates) successfully
- Tick engine starts and logs tick rate
Character Creation
- New player SSH → gets chargen flow (race + class selection)
- Chargen accepts both number and name input
- All races display with expanded info (size, traits, natural attacks, vision)
- Dragon race shows custom body slots, natural armor, fire breath, vision types
- After chargen, player appears in spawn room with correct stats
- Stats reflect race modifiers (STR, DEX, CON, INT, WIS, PER, CHA)
- Player saved to DB after creation
Player Persistence
- Reconnecting player skips chargen, sees "Welcome back!"
- Room, stats, inventory, equipment all restored from DB
- Status effects persist across logout/login (stored in DB)
- Status effects continue ticking while player is offline
- On reconnect, expired effects are gone; active effects resume
- Verify with:
sqlite3 mudserver.db "SELECT * FROM players;"
Look Command
lookwith no args shows the room (NPCs, objects, exits, players)look <npc>shows NPC description, stats, and attitudelook <object>shows object description (floor or inventory)look <exit>shows where the exit leadslook <player>shows player race, level, combat statuslook <invalid>shows "You don't see X here."
Movement & Navigation
go north,n,south,s, etc. all work- Invalid direction shows error
- Room view shows: NPCs (colored by attitude), objects, exits, other players
- Cannot move while in combat (combat lockout)
NPC Interaction
examine <npc>shows description, stats, attitude labeltalk <friendly>shows greeting dialoguetalk <hostile>shows snarl message- Dead NPCs don't appear in room view
Combat - Tick-Based
attack <npc>enters combat state with any NPC that has combat stats- Attacking friendly/neutral NPCs is allowed but incurs attitude penalties
- Attacking non-hostile NPC: attitude shift -30 individual, -15 faction
- "The locals look on in horror" message when attacking non-hostile
- Combat rounds resolve automatically on server ticks (not on command)
- Player receives tick-by-tick combat output (damage dealt, damage taken)
- Default combat action is "attack" if no other action queued
defend/defsets defensive stance (reduced incoming damage next tick)- NPC death: awards XP, shifts attitude -10, shifts faction -5
- Player death: respawns at spawn room with full HP, combat cleared, effects cleared
- NPCs respawn after configured time
- Combat lockout: can only attack/defend/flee/look/stats/inv/use/quit during combat
fleequeues escape attempt — may fail based on statsuse <item>in combat queues item use for next tick- Multiple ticks of combat resolve correctly without player input
- Combat ends when NPC dies (player exits combat state)
- Combat ends when player flees successfully
- NPCs without explicit [combat] section get default stats (20 HP, 4 ATK, 2 DEF, 5 XP)
Combat - NPC AI
- Hostile NPCs auto-engage players who enter their room
- Aggressive NPCs do NOT auto-engage (only attackable, not initiating)
- NPC attacks resolve on tick alongside player attacks
- NPCs in combat continue attacking even if player sends no commands
Combat - RNG
- Damage varies between hits (not identical each time)
- Multiple rapid attacks produce different damage values
Items & Equipment Slots
take <item>picks up takeable objectsdrop <item>places item in roomequip <weapon>equips tomain_handslot (backwards-compat via kind)equip <armor>equips to appropriate slot (objslotfield or fallback)- Equipping to an occupied slot returns old item to inventory
equipfails if race doesn't have the required slot- Objects with explicit
slotfield use that slot use <consumable>heals and removes item (immediate out of combat)use <consumable>in combat queues for next tickinventoryshows equipped items by slot name + bag itemsstatsshows equipment bonuses and natural bonuses separately
Status Effects
- Poison deals damage each tick, shows message to player
- Regeneration heals each tick, shows message to player
- Status effects expire after their duration (in ticks)
statscommand shows active status effects and remaining duration- Multiple status effects can be active simultaneously
- Negative status effects cleared on player death/respawn
- Status effects on offline players resolve by wall-clock time on next login
Weather & Time
- Outdoor rooms display time of day (e.g.,
[Night],[Morning]). - Outdoor rooms display current weather (e.g.,
The sky is clear,It is raining). - Indoor rooms do not show weather or time of day.
- Rain or storm applies the
wetstatus effect to players in outdoor rooms. - Weather changes periodically and broadcasts messages to players in outdoor rooms.
Guilds
guild listshows all available guilds with descriptionsguild info <name>shows guild details, growth stats, and spell listguild join <name>adds player to guild at level 1guild joingrants base mana/endurance from guildguild leave <name>removes guild membership- Cannot join a guild twice
- Cannot leave a guild you're not in
- Race-restricted guilds reject restricted races
- Player level requirement enforced on
guild join - Multi-guild: player can be in multiple guilds simultaneously
- Guild membership persists across logout/login (stored in player_guilds table)
statsshows guild memberships with levels
Spells & Casting
spellslists known spells grouped by guild with cost and cooldown- Spells filtered by guild level (only shows spells at or below current guild level)
cast <spell>out of combat: heal/utility resolves immediatelycast <spell>out of combat: offensive spells blocked ("enter combat first")cast <spell>in combat: queues as CombatAction, resolves on tick- Spell resolves: offensive deals damage to NPC, shows damage + type
- Spell resolves: heal restores HP, shows amount healed
- Spell resolves: utility applies status effect
- Mana deducted on cast; blocked if insufficient ("Not enough mana")
- Endurance deducted on cast; blocked if insufficient
- Cooldowns applied after casting; blocked if on cooldown ("X ticks remaining")
- Cooldowns tick down each server tick
- NPC can die from spell damage (awards XP, ends combat)
- Spell partial name matching works ("magic" matches "Magic Missile")
Chargen & Guild Seeding
- Class selection shows "→ joins " when class has a guild
- Creating a character with a class that references a guild auto-joins that guild
- Starting mana/endurance calculated from guild base + racial stat modifiers
- Guild membership saved to DB at character creation
Passive Regeneration
- Players out of combat slowly regenerate HP over ticks
- Players out of combat slowly regenerate mana over ticks
- Players out of combat slowly regenerate endurance over ticks
- Regeneration does not occur while in combat
- HP/mana/endurance do not exceed their maximums
Tick Engine
- Tick runs at configured interval (~3 seconds)
- Tick processes: NPC AI → combat rounds → status effects → respawns → regen
- Tick output is delivered to players promptly
- Server remains responsive to immediate commands between ticks
- Multiple players in separate combats are processed independently per tick
NPC Race & Class
- NPCs with fixed race/class in TOML show that race/class
- NPCs without race get a random non-hidden race at spawn
- NPCs without class: race default_class used, or random non-hidden if no default
look <npc>shows NPC race and classexamine <npc>shows NPC race and class- Rat shows "Beast Creature" (fixed race/class)
- Barkeep shows a random race + Peasant (no fixed race, human default class)
- Thief shows random race + Rogue (no fixed race, fixed class)
- Guard shows random race + Warrior (no fixed race, fixed class)
- On NPC respawn, race/class re-rolled if not fixed in TOML
- Hidden races (Beast) do not appear in character creation
- Hidden classes (Peasant, Creature) do not appear in character creation
Race System
- Existing races (Human, Elf, Dwarf, Orc, Halfling) load with expanded fields
- Dragon race loads with custom body, natural attacks, resistances, traits
- Dragon gets custom equipment slots (forelegs, hindlegs, wings, tail)
- Dragon's natural armor (8) shows in stats and affects defense
- Dragon's natural attacks (fire breath 15dmg) affect effective attack
- Items magically resize — no size restrictions on gear (dragon can use swords)
- Races without explicit [body.slots] get default humanoid slots
- Stat modifiers include PER (perception) and CHA (charisma)
- Race traits and disadvantages display during chargen
- XP rate modifier stored per race (dragon = 0.7x)
- Regen modifiers stored per race (dragon HP regen = 1.5x)
Attitude System
- Per-player NPC attitudes stored in DB
examineshows attitude label per-player- Killing NPC shifts attitude (individual -10, faction -5)
- Verify:
sqlite3 mudserver.db "SELECT * FROM npc_attitudes;"
Admin System
- Non-admin can't use
admincommands (gets error) - Set admin via mudtool:
mudtool players set-admin <name> true admin helpshows admin command listadmin promote <player>grants admin (verify in DB)admin demote <player>revokes adminadmin kick <player>disconnects target playeradmin teleport <room_id>warps to room (shows room list on invalid)admin registration offblocks new player creationadmin registration onre-enables itadmin announce <msg>broadcasts to all playersadmin healheals self;admin heal <player>heals targetadmin info <player>shows detailed stats + attitudesadmin setattitude <player> <npc> <value>modifies attitudeadmin listshows all players with online/offline status
Registration Gate
- With registration open (default), new players can create characters
- With registration off, new SSH connections get rejection message
- Existing players can still log in when registration is closed
MUD Tool - CLI
mudtool validate -w ./worldchecks world data (schemas, references, values)mudtool players listshows all playersmudtool players show <name>shows detailsmudtool players set-admin <name> trueworksmudtool players delete <name>removes player + attitudesmudtool settings listshows settingsmudtool settings set registration_open falseworksmudtool attitudes list <player>shows attitudesmudtool attitudes set <player> <npc> <value>works
MUD Tool - TUI
mudtool tuilaunches interactive interface- Tab/1/2/3 switches between Players, Settings, Attitudes tabs
- Arrow keys navigate rows
- 'a' toggles admin on Players tab
- 'd' prompts delete confirmation on Players tab
- Enter edits value on Settings and Attitudes tabs
- ←→ switches player on Attitudes tab
- 'q' exits TUI
JSON-RPC Interface
list_commandsreturns the currently handleable command list- New commands added in
commands.rsare automatically discovered loginaccepts an existing player name (requires character to be created first)- Command output is stripped of ANSI color codes for API consumption
- Verify manually with:
echo '{"_jsonrpc": "2.0", "method": "list_commands", "params": {}, "id": 1}' | nc localhost 2223
Quick Smoke Test Script
CI: each scenario is a separate step in .gitea/workflows/smoke-tests.yml (each SSH step starts mudserver, runs the block, stops it; the same TEST_DB file carries state between steps). The last step exercises JSON-RPC login / list_commands on port 2223 (expects shop in the command list). Local: ./run-tests.sh runs the full sequence in one process. When you add or change coverage, update the workflow steps and run-tests.sh together, and keep the checklist sections above aligned.