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_line(self, message: str) -> 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
    rooms: dict[UUID, Entity]   # Quick room lookup

Persistence

The DocumentStore API provides persistence:

class DocumentStore(Protocol):
    async def get(self, collection: str, id: str) -> dict | None: ...
    async def put(self, collection: str, id: str, doc: dict) -> None: ...
    async def delete(self, collection: str, id: str) -> bool: ...
    async def query(self, collection: str, filter: dict) -> list[dict]: ...

Implementations include: - InMemoryDocumentStore - Testing and development - MongoDocumentStore - Production MongoDB storage - SQLDocumentStore - SQL database storage

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