Skip to content

Architecture Overview

MAID is built on a modern, async-first architecture designed for extensibility and performance.

Package Layering

MAID follows a strict layered architecture where each package has clear responsibilities:

+------------------------------------------+
|           Content Packs                  |
|  (maid-classic-rpg, your-content-pack)   |
+------------------------------------------+
                    |
                    v
+------------------------------------------+
|           maid-stdlib                    |
|  Common components, events, utilities    |
+------------------------------------------+
                    |
                    v
+------------------------------------------+
|           maid-engine                    |
|  Core infrastructure (ECS, networking,   |
|  events, commands, storage, plugins)     |
+------------------------------------------+

Package Responsibilities

maid-engine (Core Infrastructure) - Entity Component System (ECS) foundation - Event bus for pub/sub messaging - Tick-based game loop - Network servers (Telnet, WebSocket) - Command registry and processing - Document storage API - Content pack loading and lifecycle - Authentication and session management - No game-specific logic

maid-stdlib (Standard Library) - Common components (Health, Position, Inventory, etc.) - Standard events (RoomEnter, DamageDealt, ItemPickedUp, etc.) - Core commands (look, move, get, drop) - Utility functions (dice rolling, text formatting) - Building blocks for content packs

Content Packs (Game Logic) - Game-specific systems (combat, magic, crafting) - Custom commands and events - World content (rooms, items, NPCs) - Game rules and mechanics

Async Architecture

MAID is built entirely on Python's asyncio for non-blocking I/O:

# Everything is async
async def main():
    engine = GameEngine(settings)
    engine.load_content_pack(ClassicRPGContentPack())
    await engine.start()

    # Engine runs until stopped
    await engine.run()

Benefits of Async Design

  1. High Concurrency - Handle hundreds of simultaneous connections
  2. Non-blocking I/O - Network and database operations don't block the game loop
  3. Clean Code - Async/await syntax is readable and maintainable
  4. Resource Efficient - Single-threaded event loop uses minimal resources

The Tick Loop

The game loop runs at a configurable tick rate (default: 4 ticks/second):

# Simplified tick loop
async def _tick_loop(self):
    while not self._stop_event.is_set():
        tick_start = time.monotonic()
        delta = tick_start - self._last_tick_time

        # Emit tick event
        self._world.events.emit_sync(
            TickEvent(tick_number=self._tick_count, delta=delta)
        )

        # Process world tick (all systems run)
        await self._world.tick(delta)

        # Sleep until next tick
        sleep_time = self._tick_interval - (time.monotonic() - tick_start)
        if sleep_time > 0:
            await asyncio.sleep(sleep_time)

Systems receive the delta time and can perform time-based updates:

class RegenerationSystem(System):
    async def update(self, delta: float) -> None:
        for entity in self.entities.with_components(HealthComponent):
            health = entity.get(HealthComponent)
            # Regenerate 1 HP per second
            health.current = min(
                health.current + delta,
                health.maximum
            )

Network Stack

MAID supports multiple connection types simultaneously:

                 +------------------+
                 |    MAIDServer    |
                 +------------------+
                        |
        +---------------+---------------+
        |                               |
+-------v-------+               +-------v-------+
| Telnet Server |               |   Web Server  |
|  (port 4000)  |               |  (port 8080)  |
+---------------+               +---------------+
        |                               |
+-------v-------+               +-------v-------+
|TelnetSessions |               |  WebSockets   |
| GMCP, MSDP,   |               |  JSON-based   |
| MXP, MCCP     |               |  protocol     |
+---------------+               +---------------+

Telnet Server

The Telnet server supports traditional MUD protocols:

  • GMCP (Generic MUD Communication Protocol) - Structured data exchange
  • MSDP (MUD Server Data Protocol) - Variable-based data
  • MXP (MUD eXtension Protocol) - Rich text formatting
  • MCCP2 (MUD Client Compression Protocol) - Compression
  • SSL/TLS - Secure connections
# Session lifecycle
session = TelnetSession(reader, writer)
await session.negotiate_protocols()  # GMCP, etc.
await connection_handler(session, server)  # Game logic

Web Server

The Web server provides:

  • WebSocket API - Real-time gameplay
  • REST API - Admin interface, external integrations
  • Admin UI - React-based web administration

Session Management

Sessions abstract the connection type:

class Session(Protocol):
    """Protocol for player sessions."""

    @property
    def id(self) -> UUID: ...

    @property
    def player_id(self) -> UUID | None: ...

    async def send(self, message: str) -> None: ...
    async def send_gmcp(self, package: str, data: dict) -> None: ...
    async def receive(self) -> str: ...
    async def close(self) -> None: ...

Content packs work with the Session interface, not specific implementations.

Component Interactions

Here's how the major components interact during gameplay:

Player Input                    Game Output
     |                               ^
     v                               |
+----------+                    +----------+
|  Session |                    |  Session |
+----------+                    +----------+
     |                               ^
     v                               |
+------------------+      +------------------+
| CommandRegistry  |      |    EventBus      |
+------------------+      +------------------+
     |                               ^
     v                               |
+------------------+      +------------------+
| Command Handler  |----->|    Systems       |
+------------------+      +------------------+
                                   |
                                   v
                          +------------------+
                          |    Entities      |
                          | (via components) |
                          +------------------+
  1. Player sends command through session
  2. CommandRegistry finds and executes the appropriate handler
  3. Handler modifies entities or emits events
  4. Systems process entities and events
  5. Results are sent back through the session

State Management

World State

The World class is the central state container:

class World:
    entities: EntityManager      # All game entities
    events: EventBus            # Event pub/sub
    systems: SystemManager      # ECS systems

    def register_room(self, room_id: UUID, room_data: Any) -> None: ...
    def get_room(self, room_id: UUID) -> Any | None: ...
    def all_rooms(self) -> Iterator[tuple[UUID, Any]]: ...

Persistence

The DocumentStore API provides persistence:

class DocumentStore(ABC):
    def register_schema(self, name: str, schema: type[BaseModel]) -> None: ...
    def get_collection(self, name: str) -> DocumentCollection: ...
    def has_collection(self, name: str) -> bool: ...

class DocumentCollection[T](ABC):
    async def get(self, doc_id: UUID) -> T | None: ...
    async def create(self, document: T, doc_id: UUID | None = None) -> UUID: ...
    async def update(self, doc_id: UUID, document: T) -> bool: ...
    async def delete(self, doc_id: UUID) -> bool: ...
    async def query(self, options: QueryOptions) -> list[T]: ...

Implementations include: - InMemoryDocumentStore - Testing and development - PostgresDocumentStore - Production PostgreSQL storage

Persistence Layer

MAID provides durable entity persistence through a pipeline of cooperating components:

DirtyTracker → EntityPersistenceManager → SaveScheduler → DocumentStore
  • DirtyTracker - Monitors component __setattr__ changes and flags entities as dirty
  • EntityPersistenceManager - Serializes dirty entities and coordinates save operations
  • SaveScheduler - Batches and throttles writes to avoid overwhelming the storage backend
  • DocumentStore - Abstract storage interface (InMemory, Postgres implementations)

The PersistenceSystem (from maid-stdlib) runs each tick, flushing dirty entities through this pipeline.

Migration Framework

Database schema changes are managed by the MigrationRunner:

  • Migrations are scoped to namespaces so each content pack manages its own schema independently
  • Migrations run in order at startup before content packs are loaded
  • Supports both schema migrations and data transformations

Observability Layer

MAID includes built-in operational observability:

  • ObservabilityRegistry - Central registration point for meters, tracers, and health checks
  • PrometheusMeter - Exports tick timing, event throughput, and entity counts as Prometheus metrics
  • HealthChecker - Aggregates health status from subsystems (database, network, tick loop)
  • MaidContext - Propagates request-scoped context (correlation IDs, player info) through async call chains

Data Loader Pipeline

Content is loaded from YAML files through a 6-phase pipeline:

  1. Discovery - Scan content pack data directories for YAML files
  2. Parsing - Deserialize YAML into raw dictionaries
  3. Validation - Validate against registered document schemas
  4. Resolution - Resolve cross-references between entities (e.g., room exits, NPC inventories)
  5. Instantiation - Create ECS entities and attach components
  6. Indexing - Register entities with the World (room index, grid, etc.)

NPC Cognitive Systems

Advanced NPC behavior is built from layered cognitive subsystems:

Memory → Relationships → Knowledge → Autonomy → Quest Generation
  • Memory - Episodic memories extracted from dialogue via LLM; stored per-NPC with decay
  • Relationships - Tracks disposition, trust, and history between NPCs and players
  • Knowledge - Graph of facts, rumors, and world knowledge accessible to NPCs
  • Autonomy - Needs, goals, and daily schedules that drive independent NPC behavior
  • Quest Generation - Synthesizes quests from world state, NPC knowledge, and player history

Configuration

Settings are loaded from environment variables with the MAID_ prefix:

from maid_engine.config.settings import get_settings

settings = get_settings()
print(settings.game.tick_rate)     # 4.0
print(settings.telnet.port)        # 4000
print(settings.ai.default_provider) # "anthropic"

Configuration is hierarchical using Pydantic models:

class Settings(BaseSettings):
    game: GameSettings
    telnet: TelnetSettings
    web: WebSettings
    database: DatabaseSettings
    ai: AISettings
    bridges: BridgeSettings

Hot Reload

MAID supports hot reloading content packs during development:

# Reload a content pack without restart
await engine.hot_reload.reload_pack("maid-classic-rpg")

The hot reload system: 1. Pauses the tick loop 2. Saves pack state (entities, systems) 3. Unloads the pack 4. Reloads Python modules 5. Re-registers commands, events, systems 6. Restores state 7. Resumes the tick loop

Further Reading