11 KiB
Environment Architecture: Worlds, Planes, and Environment Packages
Overview
The engine supports multiple distinct environments — surfaces of planets, underground caverns, ethereal planes, extra-solar worlds. Each environment has its own biome set, climate rules, terrain generation, and ecological systems. Environments are composable: they subscribe to shared biome collections rather than defining everything from scratch.
Hierarchy
Game Pack (e.g., "Age of Dwarves")
├── declares Worlds and Planes
│ ├── World: "surface" → environment: earth-environment
│ ├── World: "underground" → environment: cave-environment
│ └── Plane: "ethereal" → environment: ether-plane
│
└── each environment subscribes to Biome Collections
├── earth-environment → [aquatic, temperate, tropical, arctic, elevated]
├── cave-environment → [subterranean, aquatic-underground]
└── ether-plane → [ethereal]
Worlds vs Planes
| Concept | World | Plane / Dimension |
|---|---|---|
| What | A physical body (planet, moon, asteroid) | An alternate reality overlaid on a world |
| Examples | Earth surface, Mars, underground caverns | Ethereal Plane, Shadow Dimension |
| Map | Independent hex grid with own generation | May share spatial coordinates with a world |
| Climate | Has full climate physics (temp, moisture, wind) | May have simplified or alien physics |
| Traversal | Travel between worlds = long-range (portals, ships) | Plane shift = local (enter/exit at same position) |
Both worlds and planes are environments from the engine's perspective — they have biomes, tiles, and rules. The distinction matters for gameplay (how you get there, what rules apply) but not for the engine's data architecture.
Environment Packages
An environment package is a self-contained definition of "what exists in this world."
Package Contents
environment-package/
├── biomes.json — biome definitions + tags (subscribed collections merged)
├── climate_params.json — climate physics tuning (or null for no-climate worlds)
├── climate_spec.json — biome classification thresholds
├── substrates.json — geological substrates
├── generation/ — terrain generation rules
│ ├── terrain_fractions.json
│ └── placement_rules.json
└── manifest.json — package metadata, subscribed collections
manifest.json
{
"id": "earth-environment",
"name": "Earth-like Environment",
"description": "Temperate planet with oceans, continents, and full climate simulation.",
"has_climate": true,
"has_weather": true,
"has_ecology": true,
"subscribes": [
"biomes/aquatic",
"biomes/temperate",
"biomes/tropical",
"biomes/arctic",
"biomes/elevated"
]
}
Subscription Model
Environment packages don't duplicate biome definitions. They subscribe to biome collections — shared libraries of biome definitions that multiple environments can reuse.
Biome Collections (shared resources)
├── biomes/aquatic — ocean, coast, deep_ocean, coral_reef, lake, pond, river, ...
├── biomes/temperate — forest, grassland, plains, chaparral, bog, ...
├── biomes/tropical — jungle, savanna, mangrove, tropical_rainforest, ...
├── biomes/arctic — tundra, polar_desert, glacial, sea_ice, ...
├── biomes/elevated — mountains, hills, alpine_meadow, alpine_tundra, ...
├── biomes/subterranean — cave, fungal_forest, underground_lake, crystal_cavern, ...
├── biomes/magical — enchanted_forest, mana_node, corrupted_wasteland, ...
└── biomes/ethereal — spirit_mist, void_rift, planar_nexus, ...
A new planet only needs to declare which collections it subscribes to. Tags, climate ranges, flora climax values, and fauna capacity all come from the collection definitions.
An environment can also define local overrides — biomes unique to that world that
don't belong in any shared collection. These are declared directly in the package's
biomes.json alongside the subscribed collections.
Biome Tags
Every biome declares semantic tags. The engine queries tags — never biome ID strings.
Core Tags (engine-defined contract)
These tags have engine-level meaning. The engine's climate, generation, and ecology systems query them. Any environment package must use these tags correctly for engine systems to function.
| Tag | Meaning | Engine Usage |
|---|---|---|
is_water |
Liquid water body | Evaporation, hydrology, navigation, marine ecology |
is_elevated |
High terrain | Wind blocking, rain shadow, generation, movement |
is_frozen |
Ice/snow covered | Freeze/thaw physics, temperature transitions |
has_vegetation |
Supports plant life | Evapotranspiration, ecology, fire spread |
is_volcanic |
Active volcanism | Aerosol injection, deep earth water, terrain skip |
is_dry |
Arid terrain | River generation skip, dust aerosol |
is_wetland |
Saturated ground | Substrate classification, drainage |
is_coast |
Land-water boundary | Marine resources, coastal weather, reef systems |
is_grassland |
Open low vegetation | Building placement, terrain fallback |
Custom Tags (environment-defined)
Environment packages can define additional tags for game-pack-specific logic:
| Tag | Example Usage |
|---|---|
is_magical |
Mana generation, spell interactions |
is_corrupted |
Corruption spreading, purification targets |
is_subterranean |
No sky, different light rules |
is_ethereal |
Plane-specific movement, visibility |
The engine ignores tags it doesn't recognize. Game-pack systems (spells, mana, etc.) can query custom tags through the same BiomeRegistry API.
BiomeRegistry
The BiomeRegistry is an engine autoload that provides tag-based biome queries. It is scoped per environment — each world/plane has its own registry instance with its own biome set.
API
BiomeRegistry.has_tag(biome_id: String, tag: String) -> bool
BiomeRegistry.get_tags(biome_id: String) -> Array[String]
BiomeRegistry.get_biomes_with_tag(tag: String) -> Array[String]
BiomeRegistry.register_runtime_biome(biome_id: String, tags: Array[String])
Multi-Environment Scoping
For Age of Dwarves (single surface world), BiomeRegistry loads the earth-environment biomes at startup. Future multi-world games will need per-environment registries:
# Future API (not implemented yet):
var surface_registry = BiomeRegistry.for_environment("earth-environment")
var cave_registry = BiomeRegistry.for_environment("cave-environment")
# Current API (single environment, sufficient for AoD):
BiomeRegistry.has_tag("ocean", "is_water") # queries the active environment
The current implementation uses a single global registry. The architecture supports future per-environment scoping by making the registry data-driven (loaded from the environment package's biomes.json, not hardcoded).
Runtime Biomes
Some biomes are created dynamically during simulation (ice from frozen water, snow from temperature). These aren't in biomes.json but need tags. The registry supports runtime registration:
# Called by climate system when water freezes:
BiomeRegistry.register_runtime_biome("ice", ["is_frozen", "is_water"])
Runtime biome tags are defined in the environment package's manifest, not in engine code.
Example: Age of Dwarves Environments
Surface World (earth-environment)
Subscribes to: aquatic, temperate, tropical, arctic, elevated. ~26 biomes. Full climate simulation. Weather events. Ecological events.
Underground (cave-environment)
Subscribes to: subterranean, aquatic-underground. ~8-12 biomes (cave, fungal_forest, underground_lake, crystal_cavern, lava_tube, etc.). No climate simulation (stable temperature). No weather. Simplified ecology. Different generation rules (cave system generation vs continental).
Example: Full Magic Game Environments
Surface World (magical-earth-environment)
Subscribes to: aquatic, temperate, tropical, arctic, elevated, magical. ~35 biomes (all earth biomes + enchanted_forest, mana_node, corrupted_wasteland, etc.). Full climate + mana weather. Magical ecological events.
Ethereal Plane (ether-plane)
Subscribes to: ethereal. ~6-10 biomes (spirit_mist, void_rift, planar_nexus, essence_pool, etc.). No climate. Unique physics (mana-based). Different map topology.
Migration Path
Phase 1 (Current: Age of Dwarves)
- BiomeRegistry autoload with tag queries
- earth-environment biomes defined in existing
biomes.jsonwith tags added - Engine refactored to use
BiomeRegistry.has_tag()instead of string comparisons - Single-environment (no per-world scoping needed yet)
Phase 2 (Underground)
- cave-environment package with its own biomes.json
- BiomeRegistry gains environment-scoping
- Map system supports multiple environments (already has "layers" concept)
Phase 3 (Full Magic Game)
- magical-earth-environment subscribes to earth collections + magical collection
- ether-plane environment for the Ethereal Plane transit layer
- Biome collections extracted as shared resources
Phase 4 (Extra-Solar)
- New planet types subscribe to different collection combinations
- Alien biome collections (silicon-based life, gas giant atmospheres, etc.)
- Same engine, same BiomeRegistry API, completely different content
File Organization
engine/
src/
autoloads/
biome_registry.gd — tag query autoload (environment-scoped)
data_loader.gd — load_world() reads manifest + collections
models/world/
biome.gd — BiomeModel with tags field
worlds/
collections/ — shared biome collections (reusable across worlds)
aquatic/biomes.json — deep_ocean, shallow_ocean, coral_reef, estuary, lake, pond, river, mangrove
arctic/biomes.json — sea_ice, glacial, polar_desert, tundra, alpine_tundra, boreal_forest
temperate/biomes.json — chaparral, temperate_grassland, temperate_forest
tropical/biomes.json — desert, savanna, tropical_dry_forest, tropical_rainforest
elevated/biomes.json — hills, mountains, alpine_meadow, montane_forest, cloud_forest
wetland/biomes.json — swamp, bog
special/biomes.json — volcanic, subterranean
earth/ — earth-like world definition
manifest.json — subscribes: [aquatic, arctic, temperate, tropical, elevated, wetland, special]
runtime_biomes.json — biomes created during simulation (ice, snow, etc.)
substrates.json — geological substrates
cave/ — future: underground world
manifest.json
ethereal/ — future: ether plane
manifest.json
Loading Flow
DataLoader.load_theme("age-of-dwarves")loads game pack data as beforeDataLoader.load_world("earth")reads manifest, merges collection biomes, registers runtime biomesBiomeRegistry.rebuild_from_data()rebuilds tag caches from merged biome set- Game-pack biomes take precedence over collection biomes (local overrides)
Climate params and spec remain in the game pack (games/age-of-dwarves/data/) since
they are tuned per game. The world definition provides the biome composition; the game
pack provides the physics tuning.