Basic setup and first-boot instructions are in the root README.md. Runtime environment variables are documented in CONFIGURATION.md. This document covers the build system itself.
Standard build
make # server, editor, and area tools
make -C src # development server build
make -C src BUILD_PROFILE=production # production server build
cp bin/server/dms_new bin/server/dms # manually promote for a direct run
The server build has an explicit compile-time persistence dependency boundary:
make -C src PERSISTENCE_BACKEND=mariadb BUILD_PROFILE=development
make -C src PERSISTENCE_BACKEND=mariadb BUILD_PROFILE=production
make -C src PERSISTENCE_BACKEND=flatfile BUILD_PROFILE=development
mariadb is the default and includes and links the MySQL-compatible client. The
flatfile selection defines __NO_MYSQL__ and does not add the MySQL include path or
client library. Backend/profile-specific objects are kept under
bin/objects/server/<backend>/<profile>/; the maintained bin/server/dms_new path is
relinked when either selection changes, so the commands can be run sequentially without
cleaning or reusing incompatible objects. development is the default and defines
TEST_MUD; production omits all development-only behavior. The production launcher
checks the build stamp before promoting or running a binary. Each binary accepts only
its matching primary runtime mode. The legacy
mariadb-primary-flatfile-fallback token remains recognized but fails closed because
mixed per-operation authority transfer is not supported.
Mixed legacy modules still mention MySQL types while their durable operations are being
moved behind backend repositories. Flat builds resolve those declarations through
src/no_mysql/, a client-free compatibility surface whose connection, query, statement,
and thread initialization calls always fail. It neither connects nor persists data and
must not be treated as a backend; flat-file primary routes durable operations through
the corresponding file repositories instead of this compatibility surface.
The root Makefile is the maintained full-project entry point. make clean
removes compiled server, editor, and area-tool artifacts but preserves generated
world data, the active runtime, package artifacts, and runtime history. Every
compiled artifact belongs below bin/, whose contents are gitignored:
| Path | Contents |
|---|---|
bin/server/ |
Staged, active, and sanitizer server executables. |
bin/server/history/ |
Bounded runtime executable history. |
bin/areas/ |
Area editor and generation tools. |
bin/migrations/, bin/tools/ |
Standalone maintenance utilities. |
bin/objects/ |
Object and dependency files for every build flavor. |
bin/packages/ |
Debian package and package metadata. |
bin/tests/ |
Native test executables. |
Use make clean-all to remove every reproducible developer artifact. It
removes all generated contents below bin/, combined and scratch world
outputs, generated lookup tables, diagnostic-build output, coverage/profile
data, and Python/tool caches. It preserves .env, logs, player and account
data, and the hand-maintained areas/world.justice, world.tab, and
world.weather runtime inputs.
Compile flags
From the default build line:
| Flag | Meaning |
|---|---|
-std=c++20 |
All .c files are compiled as C++20 with g++. |
-DTEST_MUD |
Added only by BUILD_PROFILE=development; enables development-only commands and behavior. |
-D__NO_TESTS__ |
Excludes built-in test hooks from maintained development and production builds. |
-D__NO_MYSQL__ |
Selected by PERSISTENCE_BACKEND=flatfile; removes the client compile/link dependency. |
Redis is optional at runtime, but Hiredis and OpenSSL remain build dependencies because one server binary supports both the enabled and disabled runtime configurations.
HARDENING_FLAGS adds -Og -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fstack-clash-protection. EXTRA_CFLAGS / EXTRA_LDFLAGS are appended last
and exist so a wrapper (notably scripts/build-san.sh) can add instrumentation
without discarding the warning profile or the feature defines.
Link libraries: mysqlclient, gnutls, ssl, crypto, cjson, hiredis,
bsd, curl, xml2, z, crypt, pthread. Both backends link curl
(libcurl, the libcurl4-gnutls-dev package): it carries the SMTP path for account
password recovery, which stays disabled at runtime unless MAIL_ENABLED=TRUE.
Chaos gameplay mode is selected at runtime with CHAOS_MUD=TRUE or
CHAOS_MUD=FALSE in .env. The default example disables it. The value must
use uppercase TRUE or FALSE; numeric values are not supported. See
CONFIGURATION.md for this and the other runtime switches.
Dependency install lines are in README.md. The
packaging/ directory contains the equivs manifest for the complete developer
toolset, including Git, Python, area-generation utilities, the
compiler-provided sanitizer runtimes, Valgrind, GDB, and clang-format. It
accepts an existing MariaDB or MySQL toolchain and otherwise uses the
distribution's default MySQL-compatible packages.
Warning profile
src/Makefile's WARNING_FLAGS is -Wall -Wextra -Wpedantic -Werror plus an
explicit list of correctness and hardening diagnostics. There are no
-Wno-* exceptions, and six categories that are not implied by -Wall -Wextra are named explicitly so the guarantee is deliberate rather than
incidental:
-Wwrite-strings, -Wunused-parameter, -Wunused-variable,
-Wunused-but-set-variable, -Wmissing-field-initializers,
-Wunused-function.
Those six were global exceptions until August 2026, hiding ~9,950 diagnostics.
Clearing them surfaced real defects - five MAX_STRING_LENGTH-bounded formats
writing into 256/512-byte caller buffers, a truncated player-save INSERT
handed to MySQL as malformed SQL, and a WebSocket handler that was never wired
into its dispatcher - so the rules below exist to keep the signal trustworthy:
- Never reintroduce a blanket
-Wno-*flag, a warning pragma, or a file-wide suppression. Resolve the diagnostic instead.tests/async/test_compiler_warning_profile.pyfails if one appears. - Use
[[maybe_unused]]only where a build configuration genuinely needs the declaration - typically a parameter whose only use sits inside an inactive#if. Never to hide unexplained dead state. - Do not use
const_castor a C-style cast to satisfy-Wwrite-strings. Make the calleeconst char *when it does not write through the pointer; give it a mutable buffer when it does (seewritable_argin CODEBASE.md). - Do not delete a set-but-unused calculation until its intended effect is understood - it often marks a missing check, charge, or return value.
scripts/warning-inventory.sh performs a clean build with the six categories
enabled but non-fatal and writes deduplicated counts by category and by file to
build/warning-inventory/{raw.log,dedup.txt,report.txt} (build/ is
gitignored). The report records the compiler version and the full flag set so
counts stay comparable across runs.
Note that -Wformat-truncation=2 is fatal: snprintf into a fixed buffer must
have a bound the compiler can narrow. Format each row into its own bounded
buffer rather than appending at buf + strlen(buf) with a
MAX_STRING_LENGTH bound.
When a change touches conditional code, compile-sweep the affected files under
the non-default configurations as well - REQUIRE_EMAIL_VERIFICATION,
CTF_MUD=1, and MEMCHK are the ones in use:
g++ -fsyntax-only -std=c++20 -DTEST_MUD -D__NO_TESTS__ -DCTF_MUD=1 ... src/<file>.c
Sanitizer build
scripts/build-san.sh builds an ASan/UBSan instrumented binary for hunting
memory bugs. The sanitizer runtimes arrive with the compiler declared by the
developer dependency manifest; no separate sanitizer package is needed. Use
this build for debugging sessions, not for production.
It appends its flags through EXTRA_CFLAGS/EXTRA_LDFLAGS so the full warning
profile is preserved, builds objects into bin/objects/server-san/, and leaves
its result at bin/server/dms_san - it never touches bin/server/dms. Do not
switch it back to export CFLAGS=...: a Makefile assignment overrides an
exported variable, so the flags would silently never reach the compiler.
Usage, tool selection, and the rules for when a dynamic check is required are in MEMORY_CHECKING.md.
Area file generation
The server boots from combined area files (areas/world.wld, world.mob,
world.obj, world.zon, world.qst, world.trg, plus lookup tables). These
are generated, not hand-edited:
- Build the compilers:
make build-area-tools(producesmake_mob,make_obj,make_qst,make_shp,make_wld, andmake_zonunderbin/areas/tools/). - Generate:
make world(runsareas/m_slow, including lookup generation).
The six independent compiler builds inherit GNU Make's jobserver, so
make -j"$(nproc)" can build them in parallel. Generation scripts stop on the
first failed command instead of leaving a partially refreshed world behind.
The root target records an ignored areas/.world.stamp and skips regeneration
when every required output exists and all area sources and tools are unchanged.
Per-area source directories (areas/wld/, areas/mob/, ...) hold editable
area data; the combined outputs land in areas/world.*.
scripts/cycle_mud.sh performs both steps automatically when the helper
binaries are missing, so first boot after a fresh clone works without manual
intervention.
Verifying a build
-
Recompile check:
make -C srcmust complete without errors or warnings in touched files. -
Full build and regression gate:
make test-all. -
Smoke test on a development port (uses
duris_dev, never production):./bin/server/dms 4000 & sleep 5 && nc localhost 4000 # confirm greeting, then shut down -
If you changed persistence-related code, run the relevant focused test; see TESTING.md.