# Pre-Commit Test Checklist **Automated smoke test (same as CI):** run from the repo root: ```bash ./run-tests.sh ``` This builds the server and mudtool, starts the server with a temporary DB, runs the smoke test below (new player, persistence, admin, registration gate, combat), then cleans up. Use it locally to match what Gitea Actions run on push/pull_request. The script uses `MUD_TEST_DB` (default `./mudserver.db.test`) so it does not overwrite your normal `mudserver.db`. Prerequisites: Rust toolchain (cargo), ssh client. In CI, Rust is installed by the workflow. --- Run through the checks below before every commit to ensure consistent feature coverage. ## Build - [ ] `cargo build` succeeds with no errors - [ ] `cargo build --bin mudtool` succeeds ## 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 - [ ] `look` with no args shows the room (NPCs, objects, exits, players) - [ ] `look ` shows NPC description, stats, and attitude - [ ] `look ` shows object description (floor or inventory) - [ ] `look ` shows where the exit leads - [ ] `look ` shows player race, level, combat status - [ ] `look ` 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 ` shows description, stats, attitude label - [ ] `talk ` shows greeting dialogue - [ ] `talk ` shows snarl message - [ ] Dead NPCs don't appear in room view ## Combat - Tick-Based - [ ] `attack ` 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` / `def` sets 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 - [ ] `flee` queues escape attempt — may fail based on stats - [ ] `use ` 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 ` picks up takeable objects - [ ] `drop ` places item in room - [ ] `equip ` equips to `main_hand` slot (backwards-compat via kind) - [ ] `equip ` equips to appropriate slot (obj `slot` field or fallback) - [ ] Equipping to an occupied slot returns old item to inventory - [ ] `equip` fails if race doesn't have the required slot - [ ] Objects with explicit `slot` field use that slot - [ ] `use ` heals and removes item (immediate out of combat) - [ ] `use ` in combat queues for next tick - [ ] `inventory` shows equipped items by slot name + bag items - [ ] `stats` shows 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) - [ ] `stats` command 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 ## Guilds - [ ] `guild list` shows all available guilds with descriptions - [ ] `guild info ` shows guild details, growth stats, and spell list - [ ] `guild join ` adds player to guild at level 1 - [ ] `guild join` grants base mana/endurance from guild - [ ] `guild leave ` 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) - [ ] `stats` shows guild memberships with levels ## Spells & Casting - [ ] `spells` lists known spells grouped by guild with cost and cooldown - [ ] Spells filtered by guild level (only shows spells at or below current guild level) - [ ] `cast ` out of combat: heal/utility resolves immediately - [ ] `cast ` out of combat: offensive spells blocked ("enter combat first") - [ ] `cast ` 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 ` shows NPC race and class - [ ] `examine ` 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 - [ ] `examine` shows 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 `admin` commands (gets error) - [ ] Set admin via mudtool: `mudtool players set-admin true` - [ ] `admin help` shows admin command list - [ ] `admin promote ` grants admin (verify in DB) - [ ] `admin demote ` revokes admin - [ ] `admin kick ` disconnects target player - [ ] `admin teleport ` warps to room (shows room list on invalid) - [ ] `admin registration off` blocks new player creation - [ ] `admin registration on` re-enables it - [ ] `admin announce ` broadcasts to all players - [ ] `admin heal` heals self; `admin heal ` heals target - [ ] `admin info ` shows detailed stats + attitudes - [ ] `admin setattitude ` modifies attitude - [ ] `admin list` shows 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 ./world` checks world data (schemas, references, values) - [ ] `mudtool players list` shows all players - [ ] `mudtool players show ` shows details - [ ] `mudtool players set-admin true` works - [ ] `mudtool players delete ` removes player + attitudes - [ ] `mudtool settings list` shows settings - [ ] `mudtool settings set registration_open false` works - [ ] `mudtool attitudes list ` shows attitudes - [ ] `mudtool attitudes set ` works ## MUD Tool - TUI - [ ] `mudtool tui` launches 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 ## Quick Smoke Test Script The canonical implementation is **`./run-tests.sh`** (see top of this file). The following is the same sequence for reference; when writing or extending tests, keep `run-tests.sh` and this section in sync. ```bash # Start server in background (use -d for test DB so you don't overwrite mudserver.db) TEST_DB=./mudserver.db.test RUST_LOG=info ./target/debug/mudserver -d "$TEST_DB" & SERVER_PID=$! sleep 2 # Test 1: New player creation + basic commands ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' 1 1 look stats go north talk barkeep go south go south examine thief attack thief flee quit EOF # Test 2: Persistence - reconnect ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' look stats quit EOF # Test 3: Admin via mudtool (use same test DB) ./target/debug/mudtool -d "$TEST_DB" players list ./target/debug/mudtool -d "$TEST_DB" players set-admin smoketest true ./target/debug/mudtool -d "$TEST_DB" players show smoketest ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false ./target/debug/mudtool -d "$TEST_DB" settings list # Test 4: Admin commands in-game ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' admin help admin list admin registration on admin info smoketest quit EOF # Test 5: Registration gate ./target/debug/mudtool -d "$TEST_DB" settings set registration_open false ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 newplayer@localhost <<'EOF' quit EOF # Test 6: Tick-based combat (connect and wait for ticks) ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' 1 1 go south go south attack thief EOF # Wait for several combat ticks to resolve sleep 8 ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -p 2222 smoketest@localhost <<'EOF' stats quit EOF # Verify XP changed (combat happened via ticks) # Cleanup ./target/debug/mudtool -d "$TEST_DB" settings set registration_open true ./target/debug/mudtool -d "$TEST_DB" players delete smoketest kill $SERVER_PID ```