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¶
- High Concurrency - Handle hundreds of simultaneous connections
- Non-blocking I/O - Network and database operations don't block the game loop
- Clean Code - Async/await syntax is readable and maintainable
- 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) |
+------------------+
- Player sends command through session
- CommandRegistry finds and executes the appropriate handler
- Handler modifies entities or emits events
- Systems process entities and events
- 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 - 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:
- Discovery - Scan content pack data directories for YAML files
- Parsing - Deserialize YAML into raw dictionaries
- Validation - Validate against registered document schemas
- Resolution - Resolve cross-references between entities (e.g., room exits, NPC inventories)
- Instantiation - Create ECS entities and attach components
- Indexing - Register entities with the World (room index, grid, etc.)
NPC Cognitive Systems¶
Advanced NPC behavior is built from layered cognitive subsystems:
- 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:
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¶
- ECS Concepts - Deep dive into Entity Component System
- Event System - Event-driven architecture
- Command System - Command processing
- Content Packs - Building content packs