Scope and Precedence
- Follow the repository
AGENTS.md, then the conventions in this file.
- Keep changes narrow and preserve the style of nearby legacy code.
- Treat
.c files under src/ as C++20 sources compiled with g++.
- Preserve unrelated worktree changes and never expose local credentials or player data.
Guiding Principles
- The game thread is the sole owner of mutable
P_char and P_obj state.
- Normal mutation paths and per-pulse callbacks perform no external I/O.
- Workers receive immutable typed values, never live game-object pointers.
- Persist exact revisions and clear dirty state only after the matching acknowledgement.
- Give every non-idempotent economy, ownership, reward, and audit action a unique ID.
- Bound time, bytes, retries, and shutdown behavior at every external boundary.
- Preserve proven InnoDB, transaction, dedupe, pool, and locker-worker foundations.
- Base schema and performance changes on measured repository or development-clone evidence.
Naming
- Match nearby names and established domain terms such as PID, epic, frag, rent, and locker.
- Keep existing public type names such as
P_char and P_obj stable unless a scoped refactor requires otherwise.
- New functions should describe one action; state and revision fields should identify their domain.
- Boolean and result names should make success, retryability, dirty state, and acknowledgement explicit.
- Stable operation, event, revision, and query-site IDs must be unambiguous in code and telemetry.
Files and Structure
src/ owns server implementation and its Makefile.
tests/async/ owns focused regression, runtime, source-contract, and DB-backed tests.
migrations/ is the authoritative location for schema changes and schema verification.
scripts/ owns startup, formatting, backup, debugging, and maintenance helpers.
docs/ owns architecture, database, configuration, testing, and operational guidance.
bin/ contains all compiled artifacts and must not be committed.
- Put changes in the existing domain module unless a new boundary is required by the persistence design.
C++20 and Legacy Code
- Compile touched server code as C++20; do not rely on C-only compilation behavior.
- Follow
.clang-format for touched C/C++ lines and avoid formatting unrelated legacy code.
- Keep functions focused, but do not broaden a persistence fix into unrelated modernization.
- Check all allocation, conversion, query, and serialization bounds using existing safe helpers where available.
- Initialize every output on every success and failure path before it can influence gameplay.
- Avoid mutating equipment or affects solely to build a persistence snapshot.
Concurrency and Ownership
- Build immutable snapshot DTOs on the game thread before enqueueing worker work.
- Key ordered work by player, account, item, or other owning entity; parallelize only across independent keys.
- A worker transaction may apply revision N only when the stored revision is older than N.
- Apply worker completions on the game thread and ignore stale acknowledgements safely.
- Do not call
fork() from the running multithreaded server for player or world persistence.
- Make worker lifecycle states, operation deadlines, retry timing, pool healing, drain, and termination explicit.
- Never hold a game lock or queue mutex across database, Redis, filesystem, or allocator-heavy work.
Error Handling and Recovery
- Classify external failures as retryable, terminal, ambiguous commit, or overload outcomes.
- Retain dirty revisions and retry with bounded exponential backoff after retryable failure.
- A failed terminal save must leave the live character and inventory intact and retryable.
- Do not convert Redis, queue, or journal failure into a synchronous full save on the game thread.
- Alerts and counters must report actual durable success, failure, retry, spill, and recovery state.
- Recovery records require schema versions, checksums, unique IDs, atomic checkpoints, and idempotent replay.
- Shutdown and copyover stop new mutations, drain to a documented deadline, spill remaining work, then exit.
Database Layer
Connections
- Read
DB_HOST, optional DB_PORT, DB_USER, DB_PASSWD, and DB_NAME from the environment; never hardcode or log their values.
- MySQL or MariaDB with InnoDB is the durable system of record.
- Set and verify
utf8mb4, time zone, isolation level, SQL mode, and bounded connect/read/write deadlines on every connection.
- Require native TLS or a protected local socket/tunnel for non-local database hosts.
- Heal failed pool slots asynchronously and expose acquisition, execution, retry, and oldest-work age.
Migrations
- Put additive schema changes and their verification under
migrations/.
- Keep migrations guarded and re-runnable where practical; never alter production or run operational scripts against it.
- Use
bootstrap_multithread_safe.sql only for an empty database.
- Apply
migrations/run_migration.sh only to a backed-up development clone before promotion.
- Give new migrations immutable IDs and checksums and record success only after the operation completes.
- Validate required schema and connection invariants before boot writes lookup data.
Schema and Models
- Persist monotonic revisions on parent rows for revisioned snapshot domains.
- Keep current balance or ownership rows reconcilable with immutable ledger or audit rows.
- Use foreign keys and uniqueness constraints to enforce dedupe and ownership contracts where applicable.
- Define explicit limits and errors instead of silently truncating player, inventory, or pet data.
- Add retention or archival only with documented season, reconciliation, and audit ownership.
Queries
- Prefer typed repositories and prepared statements for new hot or sensitive paths.
- Never concatenate untrusted values into SQL; use the database client's parameter or escaping contract consistently.
- Keep hot-path lookups in memory when the source defines game memory as authoritative.
- Batch related rows and avoid N+1 queries, repeated linear scans, and per-recipient query fan-out.
- Keep predicates sargable and validate candidate indexes with representative clone data and
EXPLAIN ANALYZE.
- Log stable site ID, error code, duration, and operation ID, never raw SQL or bound private values.
Transactions and Idempotency
- Commit critical economy and ownership changes before reporting final gameplay success.
- Put balance and ledger, current owner and audit outbox, or equivalent coupled state in one transaction.
- Use mandatory unique command or event IDs for ambiguous commits and replay.
- Store outbox or inbox delivery state in the same transaction as the domain mutation.
- Return authoritative committed values and publish them to all relevant in-memory owners.
Redis and Caching
- Use Redis only for reconstructible cross-process or report cache data.
- Keep player dirty state in game memory plus the durable journal, not Redis membership alone.
- Give Redis connect and command operations bounded timeouts and guard every context before use.
- Use namespaced and versioned keys, finite TTL with jitter, explicit invalidation, and single-flight rebuilds.
- Monitor cache, dirty-state, and world-recovery domains independently.
- Never put a Redis round trip in regeneration, XP, or another per-pulse hot path.
Queues and Journals
- Use typed bounded queues with byte limits, age limits, high/low watermarks, and explicit overload behavior.
- Preallocate compact queue storage away from the hot producer path and batch compatible operations.
- Spill durable idempotent records before memory limits; unrestricted raw SQL is not a durable message format.
- Acknowledge only after destination commit and checkpoint replay only after the acknowledgement is durable.
- Track pending, oldest age, high-water mark, retries, drops, spill bytes, replay duplicates, and end-to-end latency.
Testing
- Add or update a focused regression in
tests/async/ whenever behavior changes.
- Run the smallest relevant
python3 tests/async/test_<feature>.py or run_<feature>.sh wrapper first.
- Run
make -C src after every C/C++ change; the executable is bin/server/dms_new.
- Use isolated MySQL or a backed-up development clone for schema and persistence integration tests.
- Run
make test-db only for isolated database suites and make test-all for the complete handoff gate.
- Test success, retryable failure, ambiguous commit, stale acknowledgement, duplicate replay, bounds, and shutdown paths.
- Use representative history sizes for load plans; tiny local tables are not capacity evidence.
- Run the 25-to-200-client workloads and defined fault injections only on non-production ports and data.
- Run
./scripts/format.sh for changed C/C++ lines and ./scripts/format.sh --check to verify.
- Do not commit generated world output, binaries, object files, logs, archives, or runtime player/account data.
- Keep compiler warnings at zero and use the repository sanitizer or Valgrind workflows when the risk warrants them.
- Install or respect
./scripts/install-hooks.sh; do not bypass repository checks.
Observability and Privacy
- Record query and Redis counts, latency histograms, and errors by stable site ID without values.
- Correlate external-I/O time with pulse and event-budget metrics.
- Expose queue age, revision gap, last durable revision, journal age, replay state, reconnect state, deadlocks, and lock waits.
- Remove unconditional
/tmp and per-save traces from normal persistence paths.
- Any diagnostic trace must be explicit, sampled, redacted, non-blocking, size-bounded, and rotated.
- Never commit or disclose credentials, private keys, IPs, password hashes, player descriptions, logs, or player/account data.
Documentation
- Update
docs/reference/DATABASE.md, docs/reference/ARCHITECTURE.md, README configuration guidance, and the runbook when implemented behavior changes.
- Describe actual execution boundaries, durability guarantees, deadlines, degraded modes, recovery, and operator actions.
- Keep source evidence links and acceptance commands current as code moves.
Git and Review
- Preserve unrelated worktree changes and avoid destructive Git commands.
- Keep each commit to one logical, reviewable change.
- Never add co-authors, attributions,
Claude-Session, or signed-off-by lines.
- Review behavior, failure semantics, data migration safety, and observability together.
CI/CD
Build & Test and Security run on pushes to master and on pull requests targeting
master; Code Quality additionally runs on pushes to any branch. The local
equivalents are ./scripts/format.sh --check,
python3 tests/async/test_compiler_warning_profile.py, make test-all, and
make security-check.
Infrastructure
When In Doubt
- Decide from repository and measured development-clone evidence and record material assumptions.
- Preserve live state and retryability over optimistic destructive completion.
- Prefer one durable ordered command over loosely timed cross-system side effects.
- Report any validation that could not be run.