Skip to content

Wilderness System Guide

Overview

The MAID Wilderness System provides on-demand procedural generation of outdoor areas. As players explore, rooms are generated using deterministic noise-based terrain generation, creating consistent, reproducible wilderness landscapes that can expand infinitely within defined boundaries.

Key features: - On-Demand Generation: Rooms created as players explore - Deterministic Terrain: Same coordinates always produce same terrain - Biome System: Multiple biome types with unique characteristics - Landmark Support: Fixed points of interest that anchor procedural terrain - Resource/Encounter Spawning: Procedural content spawning based on biome - Automatic Cleanup: Stale empty rooms cleaned up to manage memory - Player Tracking: Track players across wilderness rooms

Quick Start

from maid_engine.world import WildernessManager, WildernessConfig, Landmark
from maid_engine.core.world import World
from maid_engine.config.settings import Settings
from maid_stdlib.components.core import DescriptionComponent

# Create wilderness manager
world = World(Settings())
config = WildernessConfig(
    seed=42,  # Deterministic seed
    min_x=-1000, max_x=1000,
    min_y=-1000, max_y=1000,
    cleanup_interval=300.0,  # Clean up every 5 minutes
    room_max_age=600.0,  # Rooms stale after 10 minutes empty
)

# A room_factory is REQUIRED to create real rooms. Without one, the manager
# only allocates a deterministic UUID and creates NO entity and NO room index
# entry — get_or_create_room() returns a GeneratedRoom whose room_id points at
# nothing (useful only for tests). See "Custom Room Factory" below.
def room_factory(x, y, biome_name, biome_def, rng):
    # Generate name/description once — each call advances the RNG, so calling
    # generate_name(rng) twice would produce two different names.
    name = biome_def.generate_name(rng)
    description = biome_def.generate_description(rng)
    room = world.create_entity()
    room.add(DescriptionComponent(name=name, long_desc=description))
    world.register_room(room.id, {"name": name, "exits": {}})
    return room.id

wilderness = WildernessManager(world=world, config=config, room_factory=room_factory)

# Register a real room to anchor the town landmark, then add the landmark.
town_room = world.create_entity()
world.register_room(town_room.id, {"name": "Starting Town", "exits": {}})
wilderness.add_landmark(Landmark(
    coord=(0, 0),
    name="Starting Town",
    room_id=town_room.id,
    radius=5,
    biome_override="plains",
))

# Get or create a room at coordinates (uses room_factory to build a real entity)
room = wilderness.get_or_create_room(100, 200)
print(f"Room: {room.room_id}, Biome: {room.biome}")

# Get spawned content for the room
content = wilderness.get_spawned_content(100, 200)
print(f"Resources: {content.resources}")
print(f"Encounters: {content.encounters}")

# Start cleanup loop
await wilderness.start()

Core Classes

WildernessConfig

Configuration for wilderness generation and management.

from maid_engine.world import WildernessConfig

config = WildernessConfig(
    seed=42,  # Random seed for deterministic generation
    terrain_config=None,  # Custom TerrainConfig (optional)
    min_x=-5000, max_x=5000,  # X boundaries
    min_y=-5000, max_y=5000,  # Y boundaries
    cleanup_interval=300.0,  # Seconds between cleanup runs
    room_max_age=600.0,  # Seconds before stale room cleanup
    default_z=0,  # Z-level for wilderness rooms
    grid_id="wilderness",  # Grid identifier
    cleanup_batch_size=100,  # Max rooms per cleanup cycle
    cleanup_yield_interval=10,  # Yield interval during cleanup
)
Field Type Default Description
seed int 0 Random seed for deterministic terrain
terrain_config TerrainConfig \| None None Custom terrain configuration
min_x, max_x int -1000, 1000 X coordinate boundaries
min_y, max_y int -1000, 1000 Y coordinate boundaries
cleanup_interval float 300.0 Seconds between cleanup runs (minimum effective: 10.0)
room_max_age float 600.0 Seconds before stale room cleanup
default_z int 0 Z-level for all wilderness rooms
grid_id str "wilderness" Grid identifier for wilderness coordinates
cleanup_batch_size int 100 Max rooms to clean per cycle (0 = unlimited)
cleanup_yield_interval int 10 Yield to event loop every N rooms during cleanup

WildernessManager

The main interface for wilderness generation and management.

from maid_engine.world import WildernessManager, WildernessConfig
from maid_engine.core.world import World
from maid_engine.config.settings import Settings

# Create with world reference (World requires a Settings instance)
world = World(Settings())
wilderness = WildernessManager(
    world=world,
    config=WildernessConfig(seed=42),
)

# Or standalone for testing (world is optional; a standalone grid is created)
wilderness = WildernessManager(config=WildernessConfig(seed=42))

GeneratedRoom

Metadata for a procedurally generated room.

from maid_engine.world import GeneratedRoom
from uuid import UUID
import time

room = GeneratedRoom(
    room_id=UUID("..."),
    coord=(100, 200),
    biome="forest",
    created_at=time.time(),
    last_visited=time.time(),
    player_count=0,
)

# Check if room is stale
if room.is_stale(max_age=600.0):
    print("Room can be cleaned up")

Landmark

Fixed points of interest in the wilderness. A landmark's coord, radius, and biome_override shape procedural generation within its radius of influence.

Note: room_id is stored on the landmark (and included in exported grid state) as a reference for your own content, but the generator does not read it. When a wilderness coordinate is first visited, a new room entity is always created for it — a landmark does not swap in a pre-existing room. Only biome_override (plus coord/radius) changes what gets generated.

from maid_engine.world import Landmark
from uuid import uuid4

# A landmark that forces the "plains" biome within 5 tiles of the origin.
town = Landmark(
    coord=(0, 0),
    name="Starting Town",
    room_id=town_room_id,     # reference id only; not used to reuse a room
    radius=5,                 # Influence radius
    biome_override="plains",  # Force biome in radius
)

# room_id is optional and defaults to None.
dungeon = Landmark(
    coord=(500, 300),
    name="Dragon's Lair",
    radius=3,
    description="A dark cave entrance looms in the mountainside.",
    biome_override="mountains",
    metadata={"dungeon_id": "dragons_lair", "level_range": (15, 20)},
)

Terrain Generation

The wilderness uses noise-based terrain generation to create biomes.

TerrainConfig

from maid_engine.world import TerrainConfig

config = TerrainConfig(
    seed=42,
    scale=100.0,  # Larger = wider terrain features
    octaves=4,  # Detail layers
    persistence=0.5,  # Amplitude decrease per octave
    lacunarity=2.0,  # Frequency increase per octave
    water_level=0.3,  # Below this = water
    mountain_level=0.7,  # Above this = mountains
    moisture_scale=80.0,  # Moisture noise scale
    moisture_octaves=3,  # Moisture detail
    temperature_scale=150.0,  # Temperature noise scale
    base_temperature=0.5,  # 0=arctic, 1=tropical
)

TerrainGenerator

from maid_engine.world import TerrainGenerator, TerrainConfig

generator = TerrainGenerator(TerrainConfig(seed=42))

# Get terrain type at coordinate
terrain = generator.get_terrain(100, 200)  # "forest"

# Get full terrain data
data = generator.get_terrain_data(100, 200)
print(f"Type: {data.terrain_type}")
print(f"Elevation: {data.elevation}")  # 0-1
print(f"Moisture: {data.moisture}")  # 0-1
print(f"Temperature: {data.temperature}")  # 0-1
print(f"Movement Cost: {data.movement_cost}")

# Individual values
elevation = generator.get_elevation(100, 200)
moisture = generator.get_moisture(100, 200)
temperature = generator.get_temperature(100, 200)

Biome Types

The terrain generator produces these biome types:

Biome Movement Cost Conditions
water 3.0 Low elevation (< 0.3)
plains 1.0 Default for moderate elevation
forest 1.0 Moderate moisture (> 0.5)
desert 1.5 Hot (> 0.7) and dry (< 0.3)
tundra 1.5 Cold (< 0.25)
mountains 2.0 High elevation (> 0.7)
swamp 2.0 High moisture near water level
hills 1.3 Elevated terrain with moderate moisture

Biome Definitions

Biomes define room characteristics, resources, and encounters.

BiomeDefinition

from maid_stdlib.world import BiomeDefinition, ResourceSpawn, EncounterSpawn
from maid_stdlib.world import register_biome

custom_biome = BiomeDefinition(
    name="enchanted_forest",
    display_name="Enchanted Forest",
    movement_cost=1.5,
    sector_type="outdoor",
    description_templates=[
        "Ethereal light filters through ancient trees.",
        "Magical energies crackle in the air.",
    ],
    name_templates=["Mystical Grove", "Fey Crossing", "Ancient Woods"],
    resources=[
        ResourceSpawn("fey_dust", spawn_chance=0.2, min_quantity=1, max_quantity=3),
        ResourceSpawn("moonflower", spawn_chance=0.1),
    ],
    encounters=[
        EncounterSpawn("sprite", spawn_chance=0.15, min_level=3, max_level=8),
        EncounterSpawn("treant", spawn_chance=0.05, min_level=10, max_level=15),
    ],
    ambient_sounds=["Bells chime softly in the distance."],
    ambient_messages=["A butterfly made of light dances past."],
)

register_biome(custom_biome)

ResourceSpawn

from maid_stdlib.world import ResourceSpawn
from random import Random

resource = ResourceSpawn(
    resource_type="iron_ore",
    spawn_chance=0.15,  # 15% chance to spawn
    min_quantity=1,
    max_quantity=3,
)

# Roll for quantity when spawned
rng = Random(42)
quantity = resource.roll_quantity(rng)

EncounterSpawn

from maid_stdlib.world import EncounterSpawn
from random import Random

encounter = EncounterSpawn(
    encounter_type="wolf",
    spawn_chance=0.20,
    min_level=3,
    max_level=7,
    group_size_min=2,
    group_size_max=5,
)

rng = Random(42)
level = encounter.roll_level(rng)
group_size = encounter.roll_group_size(rng)

Default Biomes

Access default biome definitions:

from maid_stdlib.world import (
    get_biome,
    get_all_biome_names,
    DEFAULT_BIOMES,
)

# Get specific biome
forest = get_biome("forest")
print(f"Movement cost: {forest.movement_cost}")

# List all biomes
names = get_all_biome_names()  # ['desert', 'forest', 'hills', ...]

# Access default definitions
plains = DEFAULT_BIOMES["plains"]

Room Generation

On-Demand Generation

# Get existing room or generate new one
room = wilderness.get_or_create_room(100, 200)

# Check if room exists
if wilderness.is_generated(100, 200):
    room = wilderness.get_generated_room(100, 200)

# Get biome without generating room
biome = wilderness.get_biome(100, 200)  # "forest"

# Preview terrain without generating
info = wilderness.preview_terrain(100, 200)
print(f"Biome: {info['biome']}")
print(f"Elevation: {info['elevation']:.2f}")
print(f"Movement Cost: {info['movement_cost']}")
print(f"In Bounds: {info['in_bounds']}")

Spawned Content

# Get spawned content for a room
content = wilderness.get_spawned_content(100, 200)
if content:
    print(f"Room Name: {content.room_name}")
    print(f"Description: {content.room_description}")
    print(f"Resources: {content.resources}")  # ['wood', 'mushroom']
    print(f"Encounters: {content.encounters}")  # ['deer']

Custom Room Factory

from maid_stdlib.components.core import DescriptionComponent

def my_room_factory(x, y, biome_name, biome_def, rng):
    """Create a room entity in the world and return its UUID.

    The factory signature is (x, y, biome_name, biome_def, rng) -> UUID.
    """
    # Generate name/description once — each call advances the RNG, so calling
    # generate_name(rng) twice would produce two different names.
    name = biome_def.generate_name(rng)
    description = biome_def.generate_description(rng)
    room = world.create_entity()
    room.add(DescriptionComponent(name=name, long_desc=description))
    # Register the entity in the world's room index so players can enter it.
    world.register_room(room.id, {"name": name, "exits": {}})
    return room.id

wilderness = WildernessManager(
    world=world,
    config=config,
    room_factory=my_room_factory,
)

Note: There is no RoomComponent. Room name/description live on DescriptionComponent (name, short_desc, long_desc). Do not add a PositionComponent to a room entity — PositionComponent.room_id is a required field describing which room an entity is in, not a room's own coordinates. Wilderness placement is handled by the manager's grid, not by a PositionComponent on the room.

Landmark Management

Adding Landmarks

from maid_engine.world import Landmark

# Add a landmark
wilderness.add_landmark(Landmark(
    coord=(0, 0),
    name="Starting Town",
    room_id=town_id,
    radius=5,
))

# Add landmark with biome override
wilderness.add_landmark(Landmark(
    coord=(100, 100),
    name="Oasis",
    radius=10,
    biome_override="plains",  # Force plains in desert area
))

Querying Landmarks

# Get landmark at exact coordinate
landmark = wilderness.get_landmark(0, 0)

# Get landmark influencing coordinate (checks radius)
landmark = wilderness.get_landmark_at(3, 3)  # Within Starting Town radius

# Get all landmarks
landmarks = wilderness.get_all_landmarks()

# Find landmarks in range
nearby = wilderness.get_landmarks_in_range(50, 50, radius=100)
nearby = wilderness.landmarks_in_radius(50, 50, radius=100.0)

# Remove landmark
removed = wilderness.remove_landmark(0, 0)

Player Tracking

Tracking Player Movement

# Track player entering room
wilderness.player_entered(room_id, player_id)

# Track player leaving room
wilderness.player_left(room_id, player_id)

# Alternative: update by coordinates
wilderness.increment_player_count(100, 200)
wilderness.decrement_player_count(100, 200)

# Update last visited timestamp
wilderness.update_last_visited(100, 200)

Querying Player Locations

# Get players in a room
players = wilderness.get_players_in_room(room_id)

# Get room where player is located
room_id = wilderness.get_player_room(player_id)

# Get all occupied rooms
occupied = wilderness.get_occupied_rooms()
for room in occupied:
    print(f"{room.coord}: {room.player_count} players")

Cleanup System

Automatic Cleanup

# Start the cleanup loop
await wilderness.start()

# ... game runs ...

# Stop the cleanup loop
await wilderness.stop()

# Check if running
if wilderness.is_running:
    print("Cleanup loop active")

Manual Cleanup

# Get stale rooms
stale = wilderness.get_stale_rooms()

# Manual cleanup
removed = wilderness.cleanup_stale_rooms_sync()
print(f"Removed {removed} rooms")

# Clear all generated rooms
wilderness.clear()

Cleanup Configuration

Config Default Description
cleanup_interval 300.0 Seconds between automatic cleanup
room_max_age 600.0 Seconds before empty room is stale

Set cleanup_interval=0 to disable automatic cleanup.

Statistics and Debugging

stats = wilderness.get_stats()
print(f"Generated Rooms: {stats['total_generated']}")
print(f"Landmarks: {stats['total_landmarks']}")
print(f"Stale Rooms: {stats['stale_count']}")
print(f"Players in Wilderness: {stats['player_count']}")
print(f"Biome Distribution: {stats['biome_counts']}")
print(f"Bounds: {stats['bounds']}")

Boundary Handling

# Check if coordinate is in bounds
if wilderness.is_in_bounds(500, 500):
    room = wilderness.get_or_create_room(500, 500)
else:
    print("Outside wilderness boundaries")

# Get boundary message for movement
msg = wilderness.get_edge_description(1000, 500, "east")
if msg:
    print(msg)  # "You cannot travel further east..."

Serialization

Export State

import json

state = wilderness.export_state()
with open("wilderness.json", "w") as f:
    json.dump(state, f)

Import State

with open("wilderness.json", "r") as f:
    state = json.load(f)

wilderness.import_state(state)

Important — import_state does not recreate room entities. It restores the generated-room metadata, landmarks, spawned content, and the grid's coordinate→room-id index, but it does not recreate the ECS room entities or the world room-index entries (the implementation notes: "Room entities must be recreated separately."). Immediately after importing, world.get_entity(room_id) and world.get_room(room_id) return None for every imported coordinate, so those rooms are blank and cannot be looked at even though the grid can path to them.

Recreate the entities yourself, reusing the exported room ids (via world.create_entity(entity_id=...)) so they match the grid's index, and pull the name/description back out of the exported spawned_content:

from uuid import UUID
from maid_stdlib.components.core import DescriptionComponent

wilderness.import_state(state)

# Rebuild the ECS room entities + world room index from the export. Reuse the
# exported room_id so entities line up with the grid's coordinate index.
content_by_coord = {tuple(c["coord"]): c for c in state.get("spawned_content", [])}
for room_data in state.get("generated_rooms", []):
    room_id = UUID(room_data["room_id"])
    coord = tuple(room_data["coord"])
    content = content_by_coord.get(coord)
    name = content["room_name"] if content else room_data["biome"].title()
    description = content["room_description"] if content else ""

    entity = world.create_entity(entity_id=room_id)
    entity.add(DescriptionComponent(name=name, long_desc=description))
    world.register_room(room_id, {"name": name, "exits": {}})

The export includes: - Configuration settings - All generated rooms - All landmarks - Spawned content - Last cleanup timestamp

Builder Commands

MAID provides in-game commands for wilderness management (BUILDER access level):

@wilderness.stats                               - Show wilderness statistics
@wilderness.preview [<x> <y>]                   - Preview terrain at coordinate
@wilderness.landmark add <x> <y> <name> [--radius <n>] [--biome <type>]  - Add landmark
@wilderness.landmark remove <x> <y>             - Remove landmark
@wilderness.landmark list [--near <radius>]     - List all landmarks

Integration with Content Packs

Content packs can extend the wilderness system:

from maid_engine.plugins.manifest import ContentPackManifest
from maid_engine.plugins.protocol import BaseContentPack
from maid_engine.plugins import WildernessContentMixin
from maid_engine.world import Landmark, WildernessConfig
from maid_stdlib.world import BiomeDefinition, register_biome


class MyContentPack(BaseContentPack, WildernessContentMixin):
    @property
    def manifest(self) -> ContentPackManifest:
        return ContentPackManifest(name="my-wilderness", version="1.0.0")

    async def on_load(self, engine) -> None:
        await super().on_load(engine)

        # Register custom biomes
        register_biome(BiomeDefinition(
            name="haunted_forest",
            display_name="Haunted Forest",
            movement_cost=2.0,
            description_templates=["Twisted trees claw at a grey, silent sky."],
            name_templates=["Haunted Forest"],
        ))

        # The mixin creates a WildernessManager for engine.world (World has no
        # built-in `wilderness` attribute) and starts its cleanup loop.
        wilderness = await self.setup_wilderness(engine, WildernessConfig(seed=42))
        wilderness.add_landmark(Landmark(
            coord=(0, 0),
            name="Starting Area",
            radius=10,
        ))

    async def on_unload(self, engine) -> None:
        await self.teardown_wilderness(engine)
        await super().on_unload(engine)

Example: Creating a Wilderness Zone

from maid_engine.world import (
    WildernessManager,
    WildernessConfig,
    TerrainConfig,
    Landmark,
)
from maid_stdlib.components.core import DescriptionComponent

# Configure terrain for a temperate climate
terrain_config = TerrainConfig(
    seed=12345,
    scale=150.0,  # Larger features
    water_level=0.25,  # Less water
    mountain_level=0.75,  # Higher mountains
    base_temperature=0.6,  # Slightly warm
)

# Configure wilderness
config = WildernessConfig(
    seed=12345,
    terrain_config=terrain_config,
    min_x=-500, max_x=500,
    min_y=-500, max_y=500,
    cleanup_interval=120.0,  # Aggressive cleanup
    room_max_age=300.0,
)

# A room_factory is required to build real, enterable rooms (see "Custom Room
# Factory"). Without one the manager only allocates deterministic UUIDs.
def room_factory(x, y, biome_name, biome_def, rng):
    name = biome_def.generate_name(rng)
    description = biome_def.generate_description(rng)
    room = world.create_entity()
    room.add(DescriptionComponent(name=name, long_desc=description))
    world.register_room(room.id, {"name": name, "exits": {}})
    return room.id

wilderness = WildernessManager(
    world=world, config=config, room_factory=room_factory
)

# Add key locations
wilderness.add_landmark(Landmark(
    coord=(0, 0),
    name="Central Town",
    room_id=town_id,
    radius=8,
    biome_override="plains",
))

wilderness.add_landmark(Landmark(
    coord=(200, 150),
    name="Ancient Ruins",
    radius=5,
    description="Crumbling stone pillars mark the entrance to forgotten ruins.",
    metadata={"dungeon_id": "ancient_ruins"},
))

wilderness.add_landmark(Landmark(
    coord=(-100, 300),
    name="Hermit's Hut",
    radius=2,
    description="A small wooden hut sits alone in the wilderness.",
))

# Start the wilderness system
await wilderness.start()