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_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) |
+------------------+
- 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
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:
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