Migration Guide: v0.1 to v0.2¶
Breaking Changes
MAID v0.2 is a major architectural overhaul. This upgrade contains significant breaking changes. Please read this guide carefully and test thoroughly before upgrading production systems.
Overview¶
MAID v0.2 introduces a complete restructuring of the codebase into a monorepo with three packages:
- maid-engine: Pure infrastructure (ECS, networking, auth, storage, plugin system)
- maid-stdlib: Reusable components, base systems, and utilities
- maid-classic-rpg: Traditional MUD gameplay content pack
Estimated migration effort: Large (1-3 days depending on customizations)
Key changes at a glance:
- Monolithic codebase split into three packages
- New Content Pack system replaces old plugin architecture
- Entity Component System (ECS) for all game objects
- Async/await throughout (no more synchronous I/O)
- New configuration system with environment variables
Prerequisites¶
Before migrating, ensure you have:
- [ ] Python 3.12 or higher installed
- [ ] Read this entire guide
- [ ] Backed up your database and configuration
- [ ] Tested the upgrade in a non-production environment
- [ ] Inventoried all custom plugins/modifications
- [ ] Allocated sufficient time for testing
Breaking Changes¶
1. Package Structure¶
What changed:
The single maid package has been split into three separate packages with clear responsibilities.
Why it changed: This separation allows: - Using just the engine without game-specific content - Sharing standard components across different games - Creating custom games without modifying engine code
Migration steps:
# Before (v0.1)
from maid import Engine, Room, Player, Command
# After (v0.2)
from maid_engine.core import GameEngine
from maid_engine.core.world import World
from maid_stdlib.components import PositionComponent
from maid_classic_rpg.models import Room, Player
Installation:
# Before (v0.1)
pip install maid
# After (v0.2)
uv add maid-engine maid-stdlib maid-classic-rpg
# Or with extras:
uv sync --all-extras
2. Entity Component System (ECS)¶
What changed: Game objects (players, rooms, items, NPCs) are now ECS entities with attached components instead of class instances.
Why it changed: ECS provides: - Better composition over inheritance - Easier serialization and persistence - More flexible behavior modification - Better performance for large numbers of entities
Migration steps:
# Before (v0.1) - Class-based entities
class Player:
def __init__(self, name: str):
self.name = name
self.health = 100
self.mana = 50
self.inventory = []
player = Player("Hero")
player.health -= 10
# After (v0.2) - ECS-based entities
from maid_engine.core.ecs import Entity
from maid_stdlib.components import (
HealthComponent,
ManaComponent,
InventoryComponent,
NameComponent,
)
# Create entity and attach components
player = world.create_entity()
player.add_component(NameComponent(name="Hero"))
player.add_component(HealthComponent(current=100, maximum=100))
player.add_component(ManaComponent(current=50, maximum=50))
player.add_component(InventoryComponent())
# Access components
health = player.get_component(HealthComponent)
health.current -= 10
3. Plugin System to Content Packs¶
What changed: The old plugin system has been replaced with Content Packs, a more structured approach to extending MAID.
Why it changed: Content Packs provide: - Clear dependency management - Standardized lifecycle hooks - Better isolation between content - Layered command override system
Migration steps:
# Before (v0.1) - Old plugin system
class MyPlugin:
def __init__(self, engine):
self.engine = engine
def on_load(self):
self.engine.register_command("mycommand", self.do_command)
def do_command(self, player, args):
player.send("Hello from plugin!")
# Register plugin
engine.load_plugin(MyPlugin)
# After (v0.2) - Content Pack system
from maid_engine.plugins.protocol import ContentPack, ContentPackManifest
from maid_engine.commands.registry import CommandRegistry
from maid_engine.core.world import World
from maid_engine.core.ecs import System
class MyContentPack:
@property
def manifest(self) -> ContentPackManifest:
return ContentPackManifest(
name="my-content-pack",
version="1.0.0",
api_version="0.2",
description="My custom content",
author="Your Name",
dependencies=["maid-stdlib"],
)
def get_dependencies(self) -> list[str]:
return ["maid-stdlib"]
def get_systems(self, world: World) -> list[System]:
return []
def get_events(self) -> list[type]:
return []
def register_commands(self, registry: CommandRegistry) -> None:
registry.register(
name="mycommand",
handler=self._do_command,
category="custom",
help_text="My custom command",
)
def register_document_schemas(self, store) -> None:
pass
async def on_load(self, engine) -> None:
print("Content pack loaded!")
async def on_unload(self, engine) -> None:
print("Content pack unloaded!")
async def _do_command(self, context, args):
await context.send("Hello from content pack!")
# Load content pack
engine.load_content_pack(MyContentPack())
4. Command System¶
What changed: Commands now use a layered registry with async handlers and structured contexts.
Why it changed: - Async commands for database/network operations - Priority-based command overriding - Better command help generation - Type-safe command arguments
Migration steps:
# Before (v0.1)
@engine.command("look")
def look_command(player, args):
room = player.room
player.send(f"You are in {room.name}")
player.send(room.description)
# After (v0.2)
from maid_engine.commands.registry import CommandContext
async def look_command(context: CommandContext, args: str) -> None:
"""Look at your surroundings or an object."""
entity = context.entity
position = entity.get_component(PositionComponent)
if position and position.room_id:
room = context.world.get_room(position.room_id)
await context.send(f"You are in {room.name}")
await context.send(room.description)
# Register with priority
registry.register(
name="look",
handler=look_command,
category="movement",
help_text="Look at your surroundings",
priority=100, # Higher priority overrides lower
)
5. Event System¶
What changed: Events are now strongly typed and use an async event bus.
Why it changed: - Type safety for event handlers - Async event processing - Better event filtering - Clear event lifecycle
Migration steps:
# Before (v0.1)
@engine.on("player_move")
def on_player_move(player, from_room, to_room):
print(f"{player.name} moved from {from_room} to {to_room}")
# After (v0.2)
from maid_stdlib.events import RoomEnterEvent
from maid_engine.core.events import EventBus
async def on_room_enter(event: RoomEnterEvent) -> None:
print(f"Entity {event.entity_id} entered room {event.room_id}")
# Subscribe to event
event_bus.subscribe(RoomEnterEvent, on_room_enter)
# Emit event
await event_bus.emit(RoomEnterEvent(
entity_id=player.id,
room_id=new_room.id,
previous_room_id=old_room.id,
))
6. Configuration¶
What changed:
Configuration now uses environment variables with the MAID_ prefix and supports nested settings.
Why it changed: - 12-factor app compliance - Easier deployment configuration - Support for secrets management - Hierarchical configuration
Migration steps:
# Before (v0.1) - config.py file
config = {
"port": 4000,
"database": "sqlite:///maid.db",
"debug": False,
}
# After (v0.2) - Environment variables
# .env file or environment
MAID_TELNET__PORT=4000
MAID_WEB__PORT=8080
MAID_DB__URL=sqlite:///maid.db
MAID_DEBUG=false
MAID_GAME__TICK_RATE=4.0
# Accessing configuration
from maid_engine.config import Settings
settings = Settings()
print(settings.telnet.port) # 4000
print(settings.game.tick_rate) # 4.0
7. Async/Await Throughout¶
What changed: All I/O operations are now async.
Why it changed: - Better scalability - Non-blocking operations - Consistent async patterns - Support for async databases
Migration steps:
# Before (v0.1) - Synchronous
def save_player(player):
db.execute("UPDATE players SET ...")
return True
def send_message(player, message):
player.connection.send(message.encode())
# After (v0.2) - Asynchronous
async def save_player(player_id: str) -> bool:
await document_store.save("players", player_id, player_data)
return True
async def send_message(connection, message: str) -> None:
await connection.send(message)
Deprecated Features¶
Deprecation Notice
The following features from v0.1 are deprecated in v0.2 and will be removed in v0.3.
| Feature | Replacement | Notes |
|---|---|---|
engine.register_command() |
CommandRegistry.register() |
Use content pack command registration |
player.room direct access |
PositionComponent.room_id |
Use ECS components |
engine.on() decorator |
EventBus.subscribe() |
Use typed events |
config.py files |
Environment variables | Use MAID_ prefix |
Player class |
Entity + Components | Use ECS pattern |
Room class (direct) |
Entity + RoomComponent | Rooms are now entities |
New Features in v0.2¶
Content Pack System¶
Create modular, reusable game content.
engine.load_content_pack(StdlibContentPack())
engine.load_content_pack(ClassicRPGContentPack())
engine.load_content_pack(MyCustomContentPack())
Multi-World Support¶
Run multiple game worlds with portal connections.
world_manager = WorldManager(engine)
fantasy_world = world_manager.create_world("fantasy")
scifi_world = world_manager.create_world("scifi")
world_manager.create_portal(fantasy_world, scifi_world, "dimensional_gate")
AI Integration¶
Connect AI providers for NPC dialogue and content generation.
response = await ai_provider.generate(
prompt="The shopkeeper greets the adventurer",
context={"npc": "shopkeeper", "player": player_data}
)
Hot Reload¶
Develop faster with automatic content pack reloading.
Database Migration¶
v0.2 uses a new document-based storage system.
Export v0.1 Data¶
Import to v0.2¶
Manual Migration¶
If automatic import fails:
# Migration script example
import json
from maid_engine.storage import DocumentStore
# Load v0.1 data
with open("backup.json") as f:
old_data = json.load(f)
# Convert to v0.2 format
store = DocumentStore()
for player in old_data["players"]:
await store.save("players", player["id"], {
"name": player["name"],
"components": {
"health": {"current": player["health"], "maximum": 100},
"position": {"room_id": player["room_id"]},
"inventory": {"items": player["inventory"]},
}
})
Testing Your Migration¶
1. Install v0.2¶
2. Run Tests¶
# Run all tests
uv run pytest packages/
# Run with verbose output
uv run pytest packages/ -v
# Run specific package tests
uv run pytest packages/maid-engine/tests/
3. Test Custom Content¶
# Check content pack compatibility
uv run maid content validate my-content-pack/
# Run content pack tests
uv run pytest my-content-pack/tests/
4. Manual Testing¶
# Start server in debug mode
uv run maid server start --debug
# Connect and test basic commands
telnet localhost 4000
Rollback Procedure¶
If you need to rollback to v0.1:
-
Stop the v0.2 server:
-
Export any new v0.2 data (if needed):
-
Restore v0.1 environment:
-
Restore v0.1 database from backup
-
Start v0.1 server
Common Issues¶
ModuleNotFoundError: No module named 'maid'¶
Problem: The old maid package name no longer exists.
Solution: Update imports to use the new package names:
TypeError: object MyCoolHandler can't be used in 'await' expression¶
Problem: Command handlers must be async in v0.2.
Solution: Add async to handler functions:
# Old
def my_handler(player, args):
player.send("Hello")
# New
async def my_handler(context, args):
await context.send("Hello")
AttributeError: 'Entity' object has no attribute 'health'¶
Problem: Entities no longer have direct attributes; use components.
Solution: Access data through components:
Configuration not loading¶
Problem: v0.2 uses environment variables, not config files.
Solution: Set environment variables with MAID_ prefix:
Getting Help¶
- GitHub Issues: Report bugs
- Discussions: Ask questions
- Discord: Coming soon
Next Steps¶
After successful migration:
- Review the Content Pack Guide
- Explore the ECS Overview
- Check out Tutorials for v0.2 patterns
- Consider contributing your migration experience!