magicciv/public/games/age-of-dwarves/docs/CREATURE_ECOSYSTEM.md
2026-04-07 17:52:04 -07:00

53 KiB
Raw Permalink Blame History

Living World Architecture

Nothing is seeded. Everything emerges. Tiers are earned through survival.

The world begins as molten rock. Over billions of simulated years, oceans form, continents stabilize, soil accumulates, flora colonizes bare ground, fauna follows the food, predators follow the prey, and ecosystems mature into layered complexity. By the time a player enters, T10 ancient forests and apex megafauna are the visible survivors of geological time — irreplaceable on any civilizational timescale.

Every outcome is deterministic given the world seed. Two worlds with the same seed produce identical ecology. Nothing is placed by a designer — not biomes, not species, not lairs.

Detail Design Documents

Each layer has a dedicated design doc in games/age-of-dwarves/docs/design/:

Document Covers
SUBSTRATE_TYPES.md 10 geological substrates, elevation ranges, soil types, water body identification, derivation algorithm
BIOME_CLASSIFICATION.md Full classifier for all biomes, edge cases, threshold values
POPULATION_DYNAMICS.md Individual creature lifecycle, Lotka-Volterra with substeps, food web derivation, migration, carrying capacity, SQLite schema
SPECIES_TRAIT_CATALOG.md 7 trait categories + subterranean additions, constraints, per-biome trait weight summaries, tier derivation, spawn probability
FOOD_WEB_BALANCE.md Tuning methodology, growth rate formula, stability criteria, population curve analysis, food web pyramid verification
NPC_SETTLEMENTS.md Lair/camp/ruin emergence from fauna density, barbarian camps, abandonment transitions

This document is the architectural overview. The detail docs contain implementation-level specifications.


Principles

  1. Geological substrate is permanent — elevation, rock type, water depth. Set during Primordial/Geological phases, changed only by cataclysmic events.
  2. Biomes are emergent — substrate + climate + flora state → biome label. Change the conditions, the biome changes.
  3. Flora emerges from conditions — moisture + temperature + soil type → probability of pioneer colonization per tick. No planting.
  4. Fauna emerges from food — sufficient flora → herbivore probability. Sufficient herbivores → predator probability. No spawning.
  5. Tiers are earned — every species and tile starts at T1. Tier increases through sustained survival, successful reproduction, and ecological stability over geological time. T10 represents millennia of unbroken evolution.
  6. NPC settlements emerge from populations — high predator density → lair. Intelligent populations → camps. No placement.
  7. Corruption is a modifier — a field on the tile, not a terrain type.

Layer 1: Geological Substrate

The physical geography. Permanent unless geological events (volcanic eruption, earthquake, impact) alter it.

Properties

Every tile has:

Field Type Notes
substrate_id String Categorical label derived from elevation + geology
elevation float [0-1] Continuous. Substrate_id is a convenience label
soil_type String rocky, sandy, clay, loam, peat, volcanic_ash, permafrost
water_body_id int Which water body (-1 if land)
depth_from_coast int BFS distance from nearest land tile (-1 if land)

Substrate Categories

Substrate Typical Elevation Description
deep_water 0.0-0.15 Abyssal/deep ocean. Pelagic zone.
shallow_water 0.15-0.25 Continental shelf, neritic zone
lake_bed varies Inland freshwater body (elevation local minimum)
lowland 0.25-0.40 River valleys, coastal plains, flatlands
wetland 0.25-0.35 Saturated lowland (high water table + low drainage)
midland 0.40-0.55 Rolling terrain, foothills, plateaus
highland 0.55-0.70 Elevated terrain, mountain flanks
mountain 0.70-0.85 Above treeline, steep terrain
peak 0.85-1.0 Summit, permanent snow/ice possible
volcanic any Active volcanic geology (special substrate)

Elevation is continuous — a tile at 0.42 could be lowland or midland depending on local context. The classifier decides.

Water Bodies

Identified at map gen via flood-fill of connected water tiles:

Size Type Properties
1 tile Pond Limited ecology.
2-3 tiles Small lake Small freshwater ecosystem.
4+ tiles Lake Full freshwater ecosystem.
Connected to map edge Ocean/sea Full marine ecosystem.
Flowing (river) River Special linear water body.

depth_from_coast computed via BFS from all land tiles outward into water.


Layer 2: Biome Classification

Biomes emerge from substrate + climate (temperature + moisture) + flora state. The BiomeClassifier function takes a tile's current state and returns a biome_id. Biomes are never assigned — they are recomputed when conditions change.

biome_id replaces terrain_id across the codebase.

Earth Biomes

See BIOME_CLASSIFICATION.md for complete classifier algorithm and threshold values.

Aquatic

Biome Substrate Temperature Moisture Key Flora Key Fauna
deep_ocean deep_water any n/a phytoplankton whale, shark, large_fish
shallow_ocean shallow_water any n/a kelp, seagrass small_fish, krill, seal
coral_reef shallow_water tropical (>0.55) n/a coral reef_fish, small_fish
estuary shallow_water + river mouth temperate high reeds, seagrass small_fish, waterfowl, crocodile
lake lake_bed any n/a reeds, phytoplankton small_fish, waterfowl
pond lake_bed (size 1) any n/a reeds insects
river linear water any n/a reeds small_fish
mangrove wetland + coastal tropical high mangrove small_fish, crocodile

Tropical (temp > 0.55)

Biome Substrate Temperature Moisture Key Flora Key Fauna
tropical_rainforest lowland hot (>0.65) very wet (>0.7) tropical_trees, ferns, fungi monkey, insects, snake, big_cat
tropical_dry_forest lowland/midland hot moderate (0.4-0.7) tropical_trees, shrubs monkey, insects, snake
savanna lowland/midland hot low-moderate (0.2-0.4) grass, shrubs bison, big_cat, insects
desert lowland/midland/arid hot very dry (<0.15) cacti snake, rodents, insects

Temperate (temp 0.25-0.55)

Biome Substrate Temperature Moisture Key Flora Key Fauna
temperate_forest lowland/midland moderate wet (>0.5) deciduous_trees, ferns, fungi deer, wolf, owl, rodents
temperate_grassland lowland/midland moderate moderate (0.3-0.5) grass bison, rabbit, fox, eagle
chaparral midland moderate dry-moderate (0.15-0.35) shrubs rodents, snake, fox
swamp wetland moderate-warm saturated (>0.8) reeds, fungi, deciduous_trees crocodile, insects, waterfowl
bog wetland cool saturated (>0.7) moss_lichen, reeds insects, waterfowl

Cold (temp < 0.25)

Biome Substrate Temperature Moisture Key Flora Key Fauna
boreal_forest lowland/midland cool (0.15-0.3) moderate (>0.35) conifers, moss_lichen, fungi deer, wolf, bear, owl
tundra lowland/midland cold (0.05-0.15) low moss_lichen caribou, fox, rabbit
polar_desert any extreme cold (<0.05) very dry ice_algae seal (coast only)

Elevation

Biome Substrate Temperature Moisture Key Flora Key Fauna
montane_forest highland cool-moderate wet (>0.4) conifers, deciduous_trees deer, bear, owl
cloud_forest highland cool very wet (>0.7) tropical_trees, ferns, moss_lichen monkey, insects
alpine_meadow mountain cold moderate grass, moss_lichen mountain_goat, eagle, rabbit
alpine_tundra mountain/peak very cold low moss_lichen, ice_algae mountain_goat, eagle
permanent_ice peak extreme cold any ice_algae

Subterranean

Biome Substrate Temperature Moisture Key Flora Key Fauna
subterranean mountain/peak (has_cave) any any fungi (high), moss_lichen (low), no canopy cave insects, blind fish, giant spiders, bats

The subterranean biome requires has_cave == true set by map generation on mountain/peak tiles with appropriate geology. Cave species use unique visual modes: blind (eyeless), echolocation (bat-like), bioluminescent (self-illuminating). See SPECIES_TRAIT_CATALOG.md for subterranean trait weights.

Classification Function

BiomeClassifier.classify(tile) -> biome_id:
  if tile is water:
    return _classify_aquatic(tile)
  if tile.substrate_id == "volcanic":
    return "volcanic"

  temp = tile.temperature
  moisture = tile.moisture
  elevation = tile.elevation
  canopy = tile.canopy_cover

  # Wetland override
  if moisture > 0.7 and elevation < 0.4:
    if temp > 0.4: return "swamp"
    else: return "bog"

  # Elevation-driven
  if elevation > 0.85: return "permanent_ice" if temp < 0.1 else "alpine_tundra"
  if elevation > 0.70: return "alpine_meadow" if moisture > 0.3 else "alpine_tundra"
  if elevation > 0.55:
    if canopy > 0.4: return "montane_forest"
    if moisture > 0.7 and temp > 0.3: return "cloud_forest"
    return "alpine_meadow"

  # Temperature-driven (lowland/midland)
  if temp > 0.55:
    if moisture > 0.7 and canopy > 0.6: return "tropical_rainforest"
    if moisture > 0.4: return "tropical_dry_forest"
    if moisture > 0.2: return "savanna"
    return "desert"

  if temp > 0.25:
    if canopy > 0.5: return "temperate_forest"
    if moisture > 0.3: return "temperate_grassland"
    return "chaparral"

  if temp > 0.1:
    if canopy > 0.3: return "boreal_forest"
    return "tundra"

  return "polar_desert"

The biome is recomputed when climate or flora changes significantly. Flora dynamics (canopy growing/decaying) can shift biome classification — this IS ecological succession.


Layer 3: Flora — Emergent Vegetation

Three components tracked per tile. Each grows/decays based on climate conditions and cross-effects. Flora is never planted — it colonizes bare ground when conditions permit.

Components

Component Range Drives Cross-effects
canopy_cover [0-1] Biome classification, lumber, shade Shades undergrowth (caps at ~0.7 when dense)
undergrowth [0-1] Food yield, movement cost, habitat quality Feeds fungi (fungi needs undergrowth > 0.3)
fungi_network [0-1] Ecosystem resilience, regrowth speed, detritivore food Accelerates canopy regrowth, nutrient cycling

Spontaneous Emergence

Flora does not exist at the start of the simulation. During the Ecological Dawn phase (3-4 Gy, dt=100,000 years/tick), conditions become hospitable and pioneer flora colonizes:

Per tile, per tick:
  if temperature in [0.05, 0.95] AND moisture > 0.1 AND soil_type != "rocky":
    colonization_chance = base_rate * soil_fertility(soil_type) * moisture * dt
    if hash_roll(seed, tile, tick) < colonization_chance:
      undergrowth += pioneer_amount  # small initial colonization
      # canopy and fungi follow later through growth rules

Pioneer colonization is fast but shallow — T1-T2 weeds and scrub. Complex vegetation (canopy, fungi networks) develops through sustained growth over many ticks.

Soil fertility by type:

  • loam: 1.0 (ideal)
  • peat: 0.9 (rich organic)
  • clay: 0.7 (decent but slow drainage)
  • volcanic_ash: 0.8 (mineral-rich)
  • sandy: 0.4 (poor retention)
  • rocky: 0.1 (minimal)
  • permafrost: 0.05 (nearly impossible)

Flora Tiers (T1-T10)

Flora tier is tracked per tile and increases through sustained, uninterrupted growth. It represents the evolutionary maturity of the vegetation — how long the ecosystem has been stable.

Tier Name Character Typical Age Examples
T1 Pioneer First colonizers, fast-growing, fragile < 10 ky Lichens, mosses, pioneer grasses
T2 Scrub Low shrubs, establishing roots 10-50 ky Scrubland, heath
T3 Young growth Small trees, competitive canopy forming 50-200 ky Young woodland, meadows with saplings
T4 Established Closed canopy, developing understory 200-500 ky Maturing forest, stable grassland
T5 Mature Full three-layer structure, fungi networks forming 500 ky - 1 My Mature forest with established food webs
T6 Old growth Deep root networks, complex fungi symbiosis 1-5 My Old-growth forest, ancient grassland
T7 Ancient Multi-generational canopy layers, thick humus 5-20 My Ancient woodland, deep peat bog
T8 Primeval Ecosystem self-regulating, maximum resilience 20-50 My Primeval forest, ancient reef
T9 Legendary Exceptional stability, unique endemic flora 50-100 My One-of-a-kind groves, legendary meadows
T10 Mythic Geological-time survivors, irreplaceable > 100 My World-wonder forests, ancient coral systems

Tier advancement:

Per tile, per tick:
  if all flora components within 80% of biome climax values:
    stability_turns += 1
  else:
    stability_turns = max(0, stability_turns - 2)  # instability erodes faster

  if stability_turns >= tier_threshold[current_tier]:
    current_tier += 1
    stability_turns = 0

  # Any catastrophic drop resets progress
  if any component < 20% of previous value:
    current_tier = max(1, current_tier - 3)  # catastrophe drops tiers
    stability_turns = 0

Tier thresholds increase exponentially — reaching T10 requires geological timescales of unbroken stability. This is why T10 vegetation only exists on worlds that ran the full Ecological Maturity phase without catastrophic disruption.

Tier affects growth dynamics:

  • Higher-tier flora grows slower but is more resilient to perturbation
  • Lower-tier flora colonizes faster but is easily displaced by competition
  • T7+ flora has deep root networks that resist drought (reduced drought_decay_multiplier)
  • T9-T10 flora creates microclimates that stabilize local temperature/moisture

Growth Rules

Each component grows toward its biome's climax value when conditions (temperature, moisture) are in range. Decays when conditions worsen.

Growth rate modified by:

  • Climate match: ideal range = 1.0x, edge = 0.5x, outside = 0x (decay instead)
  • Cross-effects: fungi accelerates canopy regrowth, canopy caps undergrowth
  • Flora tier: T1-T3 grow fast (pioneer advantage), T7-T10 grow slow but resist disturbance
  • Fauna feedback: pollinators boost growth, herbivore grazing reduces undergrowth (see Feedback Loops)

Succession

When canopy_cover >= 0.8 for sustained periods AND moisture above biome threshold:

  • BiomeClassifier naturally outputs a different biome_id (canopy_cover is an input)
  • This IS succession — grassland with enough canopy becomes forest
  • No explicit transform_to rules needed — the classifier handles it
  • Flora tier carries through succession (a T7 grassland becoming forest retains T7)

Desertification

When moisture < 0.2 for sustained periods:

  • All three flora components decay at 2x rate
  • Eventually canopy -> 0, undergrowth -> near-0, fungi -> 0
  • BiomeClassifier naturally outputs desert/arid biome
  • Flora tier drops as components collapse

Regrowth (after clearing)

When a tile's flora is destroyed (fire, player clearing, event):

  • Flora tier drops to T1 regardless of previous tier
  • Regrowth follows pioneer succession: undergrowth first, then canopy, then fungi
  • Adjacent high-tier tiles accelerate regrowth via seed dispersal and fungi networks
  • Full recovery to previous tier takes geological time — this is the conservation tension
Stage Canopy Target Undergrowth Target Fungi Target Base Turns
0 barren 0.0 0.0 0.0 0
1 scrub 0.1 0.2 0.05 10
2 young growth 0.4 0.5 0.15 15
3 established 0.8 0.6 0.3 20

Flora -> Fauna Effects

Flora State Effect on Fauna
High canopy (> 0.6) Shelter: arboreal species survival +30%, predation pressure reduced
Dense undergrowth (> 0.5) Cover: ambush carnivores get hunting efficiency bonus
Fungi network (> 0.4) Detritivore food: supports decomposer populations, accelerates nutrient cycling
High flora tier (T7+) Carrying capacity bonus: mature ecosystems support more species
Pollinator-dependent flora Growth rate bonus when flying+small+herbivore fauna present (see Feedback Loops)
Flora collapse Carrying capacity crash → herbivore starvation → predator starvation cascade

Layer 4: Fauna — Emergent Populations

Fauna are trait-generated species that emerge when food sources exist. Every creature is an individual entity tracked in SQLite. Fauna is never spawned — it appears when ecological conditions produce food, and disappears when those conditions fail.

Spontaneous Emergence

Fauna does not exist at the start of the simulation. During the Ecological Maturity phase (4-4.5 Gy, dt=10,000 years/tick), flora has established sufficient biomass to support animal life:

Per tile, per tick:
  # Herbivores emerge when flora is sufficient
  if undergrowth > herbivore_emergence_threshold AND no herbivores on tile:
    if hash_roll(seed, tile, tick, "herbivore") < emergence_rate * dt:
      traits = generate_traits(biome, seed, tile, tick)
      spawn_individual(tile, traits, tier=1)

  # Predators emerge when prey is sufficient
  if herbivore_population(tile_region) > predator_emergence_threshold AND no predators in region:
    if hash_roll(seed, tile, tick, "predator") < emergence_rate * dt:
      traits = generate_predator_traits(biome, prey_traits, seed, tile, tick)
      spawn_individual(tile, traits, tier=1)

  # Detritivores emerge when organic matter accumulates
  if fungi_network > 0.3 AND no detritivores on tile:
    if hash_roll(seed, tile, tick, "detritivore") < emergence_rate * dt:
      traits = generate_detritivore_traits(biome, seed, tile, tick)
      spawn_individual(tile, traits, tier=1)

Emergence thresholds ensure fauna only appears in ecologically viable locations. The trait generator uses biome-weighted probabilities (from biome_trait_weights.json) to produce species appropriate to the environment — but the biome itself was never designed, it emerged from climate and flora.

Architecture: Traits, Not Fixed Species

Species are NOT individually authored. They emerge from trait combinations. This produces infinite variety from finite rules.

Trait System

Trait Values Gameplay Effect
size tiny, small, medium, large, huge Carrying capacity, predation hierarchy, combat stats
diet producer, herbivore, omnivore, carnivore, detritivore, filter_feeder Trophic level, food web position
habitat aquatic, terrestrial, amphibious, aerial, subterranean, arboreal Where species can live
locomotion sessile, walking, swimming, flying, burrowing, climbing, slithering Movement, migration, combat mobility
reproduction r_strategy, k_strategy Population growth rate, litter size
thermal cold_blooded, warm_blooded Climate tolerance, activity
social solitary, pack, herd, swarm, colony Group behavior, hunting efficiency, combat bonus

Trait Constraints (invalid combinations)

  • aquatic + burrowing
  • sessile + carnivore
  • aerial + huge (too heavy to fly)
  • subterranean + aerial
  • filter_feeder + terrestrial
  • sessile + walking/flying/swimming
  • arboreal + aquatic
  • swarm + huge (ecologically impossible)
  • sessile + non-colony social
  • climbing + aquatic
  • climbing + aerial
  • subterranean + arboreal
  • subterranean + herd (caves too confined)

Fauna Tiers (T1-T10) — Earned Through Survival

Every species and individual starts at T1. Tiers increase through sustained survival, successful competition, and population health over geological time. Tier is not derived from traits — it is earned.

Tier Name Character What It Took
T1 Emergent Just appeared, fragile, high mortality Existence
T2 Established Survived initial competition, breeding successfully ~50 ky of stable population
T3 Adapted Optimized for local conditions, niche secured ~200 ky, survived at least one climate shift
T4 Thriving Population at carrying capacity, stable food web ~500 ky of niche dominance
T5 Dominant Outcompeted similar species, top of local niche ~1 My, drove at least one competitor to extinction
T6 Entrenched Deep behavioral/physiological adaptation ~5 My of continuous presence
T7 Apex No natural population threats, self-regulating ~20 My, survived major ecological disruptions
T8 Ancient Multi-million-year survivors, exceptional resilience ~50 My, keystone species
T9 Legendary So adapted they shape their environment ~100 My, ecosystem-defining presence
T10 Mythic Geological-time survivors, functionally irreplaceable > 100 My, the thing legends warn about

Tier advancement mechanics:

Per species on tile, per tick:
  population_healthy = population > 0.5 * carrying_capacity
  food_web_stable = prey_available OR diet == detritivore
  no_recent_crash = population did not drop > 50% in last 10 ticks

  if population_healthy AND food_web_stable AND no_recent_crash:
    species_stability += 1
  else:
    species_stability = max(0, species_stability - 3)  # instability erodes fast

  if species_stability >= tier_threshold[current_tier]:
    current_tier += 1
    species_stability = 0

  # Catastrophic events reset progress
  if population < 0.1 * carrying_capacity:
    current_tier = max(1, current_tier - 2)
    species_stability = 0

Key properties of the tier system:

  • T1 species breed fast but die easily (r-strategy advantage at low tiers)
  • T7+ species are resilient but slow to recover from catastrophe
  • Tier determines combat stats scaling (see Combat Stats below)
  • Higher-tier fauna on higher-tier flora = maximum tile tier
  • A T10 species eliminated from a tile cannot return in gameplay timescales

Competition & Extinction

Multiple species can occupy the same niche on a tile. Competition drives the weaker one to extinction:

Per tile, per tick:
  for each pair of species sharing a niche (same diet + habitat + similar size):
    competitive_advantage = compare(species_a, species_b):
      # Higher tier = more adapted = advantage
      tier_advantage = (a.tier - b.tier) * 0.1
      # Larger population = momentum advantage
      pop_advantage = (a.population - b.population) / max(a.population, b.population) * 0.05
      # Better food web position = advantage
      food_advantage = (a.food_ratio - b.food_ratio) * 0.1

    loser.carrying_capacity *= (1.0 - competitive_pressure * dt)
    # Reduced capacity → population decline → eventual extinction or migration

Niche partitioning emerges naturally: if two similar carnivores compete and one is slightly larger, the smaller one either goes extinct, migrates to a different biome, or shifts to prey the larger one ignores. The sim doesn't code this — it falls out of the Lotka-Volterra dynamics and carrying capacity pressure.

Combat Stats (Derived from Traits + Tier)

No hand-authored creature stats. Combat capability emerges from traits and evolutionary tier:

base_hp = SIZE_HP[size] * (1.0 + tier * 0.15)
base_attack = DIET_ATTACK[diet] * SIZE_MULT[size] * (1.0 + tier * 0.1)
base_defense = SIZE_DEFENSE[size] * THERMAL_MULT[thermal] * (1.0 + tier * 0.1)
movement = LOCOMOTION_MOVE[locomotion]
vision = BASE_VISION + (1 if social in [pack, herd] else 0)

SIZE_HP = { tiny: 5, small: 12, medium: 20, large: 35, huge: 50 }
SIZE_MULT = { tiny: 0.3, small: 0.6, medium: 1.0, large: 1.5, huge: 2.0 }
SIZE_DEFENSE = { tiny: 2, small: 4, medium: 6, large: 10, huge: 16 }
DIET_ATTACK = { herbivore: 4, omnivore: 6, carnivore: 10, detritivore: 2 }
LOCOMOTION_MOVE = { sessile: 0, walking: 2, swimming: 2, flying: 3, burrowing: 1, climbing: 2, slithering: 2 }
THERMAL_MULT = { cold_blooded: 0.8, warm_blooded: 1.0 }

# Social bonuses
if social == pack: attack *= 1.3  # pack hunting
if social == herd: defense *= 1.2  # herd defense
if social == swarm: attack *= 0.5, but spawn_group_size *= 4

# Combat flags from traits
flags = []
if size == huge: flags.append("trample")
if locomotion == flying: flags.append("flying")
if diet == carnivore and social == pack: flags.append("pack_bonus")
if habitat == subterranean: flags.append("ambush")
if reproduction == r_strategy: flags.append("fast")  # respawn quickly

A T1 medium/carnivore/pack wolf has ~23 HP, ~13 attack. A T8 version of the same species has ~44 HP, ~21 attack. The ancient wolf is genuinely more dangerous — it represents a lineage that survived 50 million years of evolutionary pressure.

Fauna -> Flora Effects

Fauna State Effect on Flora
Herbivore grazing Reduces undergrowth proportional to population × grazing_by_size
Overgrazing (herbivore pop > 2× carrying capacity) Canopy damage (bark stripping), undergrowth collapse
Pollinators (flying + small/tiny + herbivore) Flora growth rate +20% on tile and adjacent tiles
Seed dispersal (herd + walking, during migration) Pioneer colonization chance on destination tiles, spread flora to barren adjacent tiles
Predator control (carnivore population healthy) Prevents herbivore overpopulation → prevents overgrazing cascade
Detritivore population Fungi network growth rate +30% (decomposition → nutrient cycling)
Fauna extinction on tile Lost pollination/dispersal/decomposition → flora growth slows
Arboreal species Seed dispersal within canopy → canopy regrowth +10%

Food Web (Derived from Traits)

No explicit diet lists. Predation rules computed from trait compatibility:

carnivore eats any fauna of strictly smaller size in same habitat
carnivore with pack social -> can prey on one size tier ABOVE (pack hunting)
carnivore eats carnivore (apex: wolf eats fox — strictly smaller only)
herbivore eats producer in same habitat AND exerts grazing pressure
omnivore eats producers AND smaller fauna
filter_feeder eats tiny aquatic producers
detritivore needs no live prey (recycles dead matter)

Size hierarchy: huge > large > medium > small > tiny
Habitat match: aquatic<->aquatic, terrestrial<->terrestrial, amphibious<->both

Without the pack bonus, huge herbivores would be invulnerable. Pack carnivores of size large can prey on huge herbivores — matching real ecology (wolf packs take down bison).

Individual Creature Lifecycle

Every creature is an individual entity with its own tier, age, health, and lifecycle.

Tier progression (aging):

Spawn -> T1 (young)
  -> survive + feed -> age_turns accumulates
  -> species tier increases globally through competition (see above)
  -> individual health degrades past 80% max age
  -> death -> loot drop scaled by tier at death

Loot on death:

Creature Tier Drop Chance Loot Character
T1-T2 5% Scraps (bone fragment, hide scrap)
T3-T4 15% Basic (pelt, meat, horn)
T5-T6 35% Quality (dire pelt, trophy, rare material)
T7-T8 60% Exceptional (ancient fang, mega-bone, unique crafting material)
T9-T10 100% Legendary (one-of-a-kind trophy, mythic material)

Flora loot follows the same pattern — chopping a T10 ancient tree yields mythic-quality wood. Harvesting T3 crops yields basic grain.

Reproduction: Healthy (health > 0.8), mature (age > maturity threshold) creatures on tiles with carrying capacity available spawn new creatures at T1.

Population Dynamics (Lotka-Volterra)

Per tile, each creature is an individual entity (stored in SQLite). Each turn uses substeps to prevent discrete-timestep instability:

SUBSTEPS = 4
dt_sub = 1.0 / SUBSTEPS

For each tile with populations:
  For substep in range(SUBSTEPS):
    For each species on tile (sorted by trophic level: producers first):
      prey_availability = sum(population of prey species on tile and neighbors)
      carrying_capacity = base_capacity * biome_match * flora_density * flora_tier_bonus
      noise = hash_noise(seed, tile, species, turn, substep) * 0.05

      if trophic == "producer":
        growth = growth_rate * population * (1 - population / carrying_capacity)
        grazing = sum(herbivore_pop * grazing_rate for each herbivore on tile)
        growth -= grazing
      elif trophic == "herbivore":
        food_ratio = prey_availability / (population * consumption_rate + 0.01)
        growth = growth_rate * population * min(1.0, food_ratio) * (1 - population / carrying_capacity)
      elif trophic == "predator":
        food_ratio = prey_availability / (population * consumption_rate + 0.01)
        growth = growth_rate * population * min(1.0, food_ratio) - death_rate * population

      # Predation pressure (Holling Type II functional response)
      for prey species matching food_web rules:
        consumed = predation_rate * population * prey_pop / (prey_pop + half_saturation)
        prey_pop -= consumed * dt_sub

      population = max(0, population + (growth * (1.0 + noise)) * dt_sub)

  # Post-substep: migration if below min_viable
  if population < min_viable_population and population > 0:
    attempt_migration(tile, species, migration_range)

  # Extinction
  if population <= 0:
    remove_species_from_tile(tile, species)

Key stability measures:

  • Substeps prevent one-turn extinction cascades
  • Stochastic noise (seed-based) prevents synchronized population crashes
  • Holling Type II saturation prevents predators consuming infinite prey
  • Herbivore grazing pressure on producers creates bottom-up regulation
  • min_viable_population threshold triggers migration before extinction

Migration

When a population drops below min_viable_population:

  1. Search tiles within migration_range for compatible tiles with capacity
  2. If found: move population to best candidate (highest carrying capacity)
  3. If not found: population goes extinct on this tile

When a population exceeds carrying capacity:

  1. Excess disperses to adjacent compatible tiles with available capacity
  2. If no room: die-off at 10%/turn until at capacity

Seed dispersal during migration: Walking + herd species carry flora seeds. Destination tiles get a pioneer colonization boost. This is how flora spreads to isolated barren tiles that conditions alone wouldn't reach.

Seasonal Migration

Species with habitat: "aerial" + social: "herd" or "swarm" can migrate seasonally.

  • Trigger: Temperature drops below species' preferred range
  • Behavior: Seasonal migrants move toward warmer biomes at configurable intervals
  • Return: When temperature recovers, migrants return if capacity available
  • Survival: Migrants must find a compatible biome within migration_range or health declines

Flavor System (Visual Identity)

Traits define biology. The FlavorGenerator transforms biology into game identity.

FlavorGenerator.generate(traits, biome, tier) -> CreatureFlavor:

  • Name: procedural two-word [Prefix] [Archetype]
  • Visual motifs: horns, scars, markings, armor, crystalline growths — scaled by tier
  • Color palette: biome-coherent, creature-specific
  • Silhouette modifiers: what makes this creature's outline instantly readable
  • Lore seed: one-sentence flavor text

Name generation:

Source Component Examples
Biome Prefix temperate_forest: Iron, Moss, Bark, Grey, Amber
deep_ocean: Abyssal, Trench, Deep, Ancient
subterranean: Pale, Crystal, Cave, Blind
tundra: Frost, Winter, Howling, White
desert: Sand, Dune, Sun, Ember
volcanic: Obsidian, Cinder, Ash, Molten
Locomotion+Diet+Size Archetype walking+carnivore+medium: Prowler, Stalker, Hunter, Fang
walking+herbivore+large: Grazer, Strider, Roamer, Stag
swimming+carnivore+huge: Trencher, Leviathan, Devourer
flying+carnivore+medium: Raptor, Screamer, Talon
burrowing+omnivore+small: Skitterer, Digger, Gnawer

Tier visual descriptors:

Tier Size Modifier Condition Presence
T1-T2 Undersized, juvenile Scrappy, patchy, unremarkable Barely noticed, blends in
T3-T4 Normal adult Healthy, standard markings Standard wildlife
T5-T6 Prime adult, muscular Well-fed, distinctive features Noteworthy, players take notice
T7-T8 Large for species, battle-scarred Thick hide, unique features, ancient eyes Impressive, stories told about it
T9-T10 Massive, ancient Moss/barnacle-covered, weathered, otherworldly The thing legends warn about

Each species x tier = unique sprite. A T3 Ironback Prowler (young grey wolf) and T9 Ironback Prowler (ancient scarred alpha, moss on haunches, one milky eye) are visually distinct creatures.

Art direction: DOTA-style dark fantasy. Every creature looks weathered, ancient, dangerous. Strong silhouettes at small sprite scale. Saturated but earthy palettes. No cartoony or anime aesthetics.


Layer 5: NPC Settlements — Emergent from Populations

Lairs, camps, and ruins are NPC buildings that emerge from ecological and population conditions, not placed by map generation.

Settlement Types

Type Emergence Condition Character
Lair Predator population on tile exceeds lair_threshold for lair_formation_ticks Den/nest of dangerous fauna, hostile encounter
Barbarian Camp Intelligent NPC population (dwarves in AoD) reaches camp_threshold on suitable terrain Hostile/neutral dwarven outpost, loot and XP
Ruin Former lair/camp where population went extinct or was cleared Explorable, may contain loot, can be recolonized

All are NPC buildings with placement: "map", owner: WILDS_PLAYER (-1).

Lair Emergence

Lairs are not placed — they crystallize from high fauna density:

Per tile, per tick:
  predator_density = count_predators(tile, radius=2) / area
  if predator_density > lair_threshold:
    lair_formation_progress += 1
  else:
    lair_formation_progress = max(0, lair_formation_progress - 1)

  if lair_formation_progress >= lair_formation_ticks:
    create_npc_building(tile, type="lair", associated_species=dominant_predator(tile))
    # Lair concentrates nearby predators, gives them roaming/detection behavior

Lair properties (derived from fauna):

  • Lair tier = dominant species tier (a lair of T7 wolves is much harder than T2 wolves)
  • Creature count = local population count (capped by max_creatures_per_lair)
  • Rewards scale with lair tier (higher tier = better loot)
  • Aggression/roaming derived from species traits (pack = roaming, solitary = territorial)

Clearing a lair:

  • Kills associated creatures → reduces local predator population
  • Feeds back into Lotka-Volterra dynamics (prey population may spike)
  • Lair becomes a ruin
  • If predator population recovers, a new lair may form

Barbarian Camps (Age of Dwarves)

In AoD, the only intelligent NPC race is dwarves. Barbarian camps represent dwarven outposts that refused civilization:

Per region, per tick (Pre-civilization phase only):
  if suitable_terrain(tile) AND resource_availability > camp_threshold:
    if hash_roll(seed, tile, tick, "camp") < camp_emergence_rate * dt:
      create_npc_building(tile, type="barbarian_camp", race="dwarf")
      spawn_garrison(tile, tier=regional_avg_tier)

Camp properties:

  • Garrison units are dwarven warriors with tier-scaled stats
  • May produce patrols that roam within a radius
  • Can be cleared for gold, equipment, population
  • No diplomacy or city-state evolution in AoD (expansion feature)
  • Camps that lose their garrison become ruins

Ruins

Former lairs or camps where populations went extinct:

  • Loot tables based on what the settlement was (lair → beast trophies, camp → equipment/gold)
  • May be recolonized if fauna density rises again (lair) or new NPCs arrive (camp)
  • Degraded rewards compared to active settlements
  • Visual: crumbling structures, overgrown

Roaming and Aggression

Creatures associated with lairs and camps have tactical behavior:

Behavior Description
Guardian Stays within detection_radius of lair/camp, attacks intruders
Roaming Patrols within leash_radius, returns when enemies defeated
Aggressive Actively seeks targets within detection_radius
Territorial Attacks anything entering the tile, does not pursue

Behavior derived from species traits:

  • pack social → roaming (coordinated patrol)
  • solitary social → territorial (ambush defense)
  • herd social → guardian (passive unless provoked)
  • swarm social → aggressive (attacks on detection)

Bidirectional Feedback Loops

The core engine of ecosystem dynamics. Every arrow has a reverse.

Flora -> Fauna

Flora grows → carrying capacity increases → fauna populations grow
High canopy → shelter for arboreal species, reduced predation
Dense undergrowth → cover for ambush predators, nesting sites for small fauna
Fungi network → food for detritivores, nutrient cycling
High flora tier → more niches available, supports higher-tier fauna
Flora collapse → carrying capacity crash → herbivore starvation → predator starvation

Fauna -> Flora

Herbivore grazing → undergrowth reduction (controlled grazing = healthy turnover)
Overgrazing → canopy damage, undergrowth collapse, desertification risk
Pollinators present → flora growth rate +20% (flying + small + herbivore)
Herd migration → seed dispersal to destination tiles
Predator control → herbivore population stays at carrying capacity → prevents overgrazing
Detritivores → fungi network growth +30% (decomposition → nutrients)
Arboreal species → seed dispersal within canopy zone
Fauna extinction → lost pollination, dispersal, decomposition → flora growth stalls

The Conservation Cascade

The central gameplay tension:

Player clearcuts T8 forest
  → flora tier drops to T1 (pioneer scrub)
  → carrying capacity crashes
  → herbivores starve or migrate
  → predators follow prey or starve
  → pollinators gone → remaining flora grows slower
  → detritivores gone → fungi network collapses
  → nutrient cycling stops → soil degrades
  → tile tier drops from T8 to T1-T2
  → recovery to T8 would take millions of simulated years
  → on gameplay timescales, the damage is permanent

The reverse cascade (rewilding) is also possible but slow:

Player protects degraded area (no harvesting, no building)
  → pioneer flora colonizes (T1-T2 scrub)
  → undergrowth provides minimal carrying capacity
  → T1 herbivores emerge
  → grazing pressure keeps undergrowth in check (healthy)
  → canopy begins growing (T2-T3)
  → small predators emerge, control herbivore population
  → pollinators arrive → flora growth accelerates
  → detritivores establish → fungi network forms
  → positive feedback loop → but tier advancement is logarithmic
  → T4-T5 achievable in gameplay (hundreds of turns in early eras)
  → T7+ would require thousands of turns — effectively impossible in a single game

Tile Tier (T1-T10) — Ecology Composite

Tile tier is a derived property of the tile's ecology state. Computed per turn.

Composite Score

tier_score = (
    flora_health * 0.25 +        # avg(canopy, undergrowth, fungi) / biome climax
    flora_tier * 0.20 +           # flora evolutionary maturity (normalized to 0-1)
    fauna_diversity * 0.20 +      # species_count / expected_species_for_biome
    fauna_tier_avg * 0.15 +       # average fauna tier on tile (normalized to 0-1)
    biome_stability * 0.10 +      # BiomeClassifier agrees with current biome_id
    population_balance * 0.10     # predator-prey near equilibrium
)

Tier Thresholds

Tier Name Score Range Yield Modifier Rarity
T1 Barren < 0.1 -4 food/prod/trade Newly formed / devastated
T2 Sparse 0.1-0.2 -2 food/prod/trade Recently colonized
T3 Scrubland 0.2-0.3 -1 food/prod/trade Pioneer vegetation
T4 Common 0.3-0.4 baseline Standard terrain
T5 Established 0.4-0.5 +1 food/prod/trade Good settlement site
T6 Rich 0.5-0.6 +2 food/prod/trade Desirable territory
T7 Pristine 0.6-0.7 +3 food/prod/trade Strategic objective
T8 Ancient 0.7-0.8 +4 food/prod/trade Named landmarks
T9 Legendary 0.8-0.9 +5 food/prod/trade ~3-5 per map
T10 Mythic > 0.9 +6 food/prod/trade ~0-2 per map, world wonders

Natural Wonders

T9-T10 tiles ARE natural wonders. No separate "natural wonder" feature needed. A T10 coral reef IS the Great Barrier Reef. A T9 ancient forest IS a world wonder. Natural wonders aren't placed — they emerge from geology, climate, flora, and fauna all sustaining peak conditions for geological time.

  • T8 Ancient: ~5-10 per map. Strategic objectives. Named landmarks (FlavorGenerator names the tile: "The Ironwood Stand", "Frostpeak Aerie").
  • T9 Legendary: ~3-5 per map. Named wonders. Unique resources, exceptional fauna.
  • T10 Mythic: ~0-2 per map. True world wonders. Irreplaceable. Worth going to war over.

Natural wonders degrade — overharvest a T10 forest and it drops. The wonder is gone. Conservation is gameplay.

What Drives Tier Up/Down

Tier rises when:

  • Flora components at or above biome climax values
  • Flora tier advancing (long-term stability)
  • Full food web present (producer + herbivore + predator + detritivore)
  • Biome is stable (climate supports current classification)
  • Lotka-Volterra populations near equilibrium
  • Pollinator-flora feedback loop active

Tier drops when:

  • Deforestation (canopy cleared → flora health/tier drops)
  • Species extinction (predator or prey gone → fauna diversity drops)
  • Climate drift (biome should reclassify → stability drops)
  • Population crash (predator starvation cascade → balance drops)
  • Player exploitation (overfishing, overharvesting, building over nature)
  • Catastrophic events (volcanic eruption, meteor, wildfire)

Tile tier determines sprite variant. Each biome has T1-T10 sprite variants. A temperate_forest at T9 shows ancient trees with wildlife; at T2 it shows sparse saplings and bare soil.

World Distribution

The tile tier distribution tells the geological story of the world:

World Type T1-T3 T4-T6 T7-T8 T9-T10
Young (1 Gy) 80% 20% 0% 0%
Mature (3 Gy) 40% 45% 14% 1%
Earth-like (4.5 Gy) 20% 45% 28% 7%
Ancient (6 Gy) 15% 35% 35% 15%

A young world is mostly barren/sparse with pockets of pioneer life. An ancient world is lush but may have resource-depleted areas. The distribution is emergent — these percentages are observed results, not targets.


Pre-Game Evolution Sequence

Maps directly to TIMESCALES.md phases. Everything is deterministic via world seed.

Ecological Dawn (3-4 Gy, dt=100,000 yr/tick, ~10,000 ticks)

What happens: First life appears.

  • Pioneer flora colonizes tiles where moisture/temperature/soil permit
  • Flora starts at T1, begins slow tier advancement
  • No fauna (insufficient biomass)
  • Biomes begin emerging from climate + flora state
  • Succession begins (grassland → scrubland → young forest where conditions support it)
  • Flora reaches T1-T3 by end of phase

Ecological Maturity (4-4.5 Gy, dt=10,000 yr/tick, ~50,000 ticks)

What happens: Complex ecosystems form.

  • Flora continues advancing tiers (T3-T7 achievable)
  • Herbivores emerge on tiles with sufficient flora (undergrowth > threshold)
  • Predators emerge where herbivore populations are sufficient
  • Detritivores emerge where fungi networks develop
  • Lotka-Volterra competition begins — overstocked niches thin out
  • Weaker species go extinct, survivors advance tiers
  • Bidirectional feedback loops activate:
    • Pollinators boost flora → flora supports more fauna → more pollinators
    • Predators control herbivores → prevents overgrazing → flora thrives
    • Detritivores boost fungi → fungi boosts canopy → more habitat
  • Food webs stabilize (or crash and reform after disruptions)
  • Climate events (volcanic eruptions, ice ages) periodically disrupt and reshape
  • Surviving species through disruptions earn rapid tier advancement
  • Lairs begin forming from high predator density (late in this phase)
  • By end: rich ecosystems with T4-T7 fauna, T5-T8 flora in stable regions

Pre-Civilization (last 100 ky, dt=1,000 yr/tick, ~100 ticks)

What happens: Final maturation and NPC settlement.

  • Stable ecosystems reach peak tiers (T8-T10 in undisturbed regions)
  • Natural wonders crystallize (T9-T10 tiles that sustained excellence)
  • NPC settlements form (barbarian camps in AoD, free people havens in expansion)
  • Ley lines mature (magic system)
  • Final food web equilibration
  • This is the world the player enters

Post-Generation Snapshot

When the player starts the game, the world has:

  • A complete tier distribution reflecting billions of years of simulated history
  • Mature ecosystems with species that earned their tiers through competition
  • NPC settlements (lairs, camps) that emerged from population dynamics
  • Ruins where former populations went extinct
  • Natural wonders (T9-T10 tiles) in geologically stable regions
  • A fossil record implicit in the tier distribution (areas of past catastrophe visible as lower-tier patches)

Storage Architecture

Static Definitions (JSON)

Read-only game data, loaded by DataLoader at game init:

resources/ecology/
  biomes/biomes.json             — biome definitions
  biomes/substrates.json         — substrate definitions
  traits/trait_definitions.json  — trait categories + values
  traits/trait_constraints.json  — invalid combinations
  traits/biome_trait_weights.json — per-biome generation weights
  traits/food_web_rules.json     — predation from traits
  traits/flavor.json             — name prefixes/archetypes, visual motifs
  flora/vegetation.json          — growth rates, cross-effects
  flora/succession.json          — succession params
  flora/desertification.json     — desertification params
  fauna/marine.json              — marine ecology params
  fauna/land.json                — land ecology params
  fauna/air.json                 — air ecology params
  ecosystem/health.json          — tier score weights and thresholds
  ecosystem/food_yield.json      — food yield curves by tier
  ecosystem/stability.json       — stability params
  npc/settlement_params.json     — lair/camp emergence thresholds
  npc/loot_tables.json           — rewards by settlement type and tier
  npc/behavior.json              — roaming, aggression, detection params

Dynamic State (SQLite)

Runtime population state, queried and updated each turn:

CREATE TABLE species (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    trait_hash TEXT UNIQUE,
    name TEXT,
    size TEXT,
    diet TEXT,
    habitat TEXT,
    locomotion TEXT,
    reproduction TEXT,
    thermal TEXT,
    social TEXT,
    tier INTEGER DEFAULT 1,
    stability_ticks INTEGER DEFAULT 0,
    growth_rate REAL,
    carrying_capacity REAL,
    migration_range INTEGER,
    maturity_age INTEGER,
    max_age INTEGER
);

CREATE TABLE creatures (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    tile_col INTEGER,
    tile_row INTEGER,
    species_id INTEGER,
    tier INTEGER DEFAULT 1,
    age_turns INTEGER DEFAULT 0,
    health REAL DEFAULT 1.0,
    FOREIGN KEY (species_id) REFERENCES species(id)
);

CREATE INDEX idx_creatures_tile ON creatures(tile_col, tile_row);
CREATE INDEX idx_creatures_species ON creatures(species_id);
CREATE INDEX idx_creatures_tier ON creatures(tier);

CREATE TABLE water_bodies (
    id INTEGER PRIMARY KEY,
    type TEXT,
    size INTEGER,
    tile_count INTEGER
);

CREATE TABLE water_body_tiles (
    body_id INTEGER,
    tile_col INTEGER,
    tile_row INTEGER,
    depth_from_coast INTEGER,
    FOREIGN KEY (body_id) REFERENCES water_bodies(id)
);

CREATE TABLE food_web (
    predator_id INTEGER,
    prey_id INTEGER,
    consumption_rate REAL,
    PRIMARY KEY (predator_id, prey_id),
    FOREIGN KEY (predator_id) REFERENCES species(id),
    FOREIGN KEY (prey_id) REFERENCES species(id)
);

CREATE TABLE npc_settlements (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    tile_col INTEGER,
    tile_row INTEGER,
    type TEXT,
    tier INTEGER DEFAULT 1,
    associated_species_id INTEGER,
    formation_tick INTEGER,
    is_ruin INTEGER DEFAULT 0,
    FOREIGN KEY (associated_species_id) REFERENCES species(id)
);

SQLite ships with Godot (no external dependency). The ecology DB file is part of the save game.

Sprite Manifest (separate DB)

games/age-of-dwarves/data/sprites.db — read-only game data, produced by sprite-gen install.

CREATE TABLE sprites (
    entity_type TEXT,
    entity_id TEXT,
    tier INTEGER,
    variant INTEGER DEFAULT 0,
    path TEXT NOT NULL,
    width INTEGER,
    height INTEGER,
    PRIMARY KEY (entity_type, entity_id, tier, variant)
);

Tile render stack (bottom to top):

  1. SpriteManifest.get_sprite("substrate", tile.substrate_id) — geological base
  2. SpriteManifest.get_sprite("biome", tile.biome_id, tile.tier) — vegetation overlay at tier
  3. Flora density overlay (procedural from canopy_cover/undergrowth)
  4. SpriteManifest.get_sprite("species", species.trait_hash, species.tier) — per visible population
  5. SpriteManifest.get_sprite("settlement", settlement.type, settlement.tier) — NPC settlements
  6. Unit sprites (existing system)

Sprite Generation

The sprite-gen pipeline generates art assets for each entity x tier combination.

Categories

Category Source Count Prompt Style
species Trait-generated via SpeciesGenerator + FlavorGenerator per unique trait hash x relevant tiers DOTA-style: FlavorGenerator provides name + motifs + tier descriptor
substrate Geological substrate types ~10 Top-down hex tile, geological texture, no vegetation
biome_grid Biomes from biomes.json per biome x T1-T10 Top-down hex tile with vegetation appropriate to biome and tier
settlement NPC settlement types lair, camp, ruin x relevant tiers Top-down hex overlay, DOTA-style structures

Asset Paths

games/age-of-dwarves/
  assets/sprites/
    substrate/      — lowland.png, highland.png, shallows.png...
    biome/          — temperate_forest_t1.png ... _t10.png (per biome x tier)
    species/        — {trait_hash}_t{N}.png (per unique trait x tier)
    settlements/    — lair_t3.png, barbarian_camp_t5.png, ruin.png...
    units/          — existing pattern
  data/
    sprites.db      — manifest: entity_type + entity_id + tier -> path

Turn Integration

Ecology runs once per full game turn, after all players act, after climate:

TurnManager.next_player() (after all players):
  1. NPC settlement AI (roaming, aggression checks)
  2. Diplomacy
  3. Protection effects
  4. Climate processing:
     a. marine_harvest.process_turn()
     b. weather.process_turn()
     c. climate.process_turn(dt)
     d. climate_effects.process_turn(dt)
     e. ecology.process_turn(dt)
        -> flora.process_turn(dt)         # growth, succession, desertification
        -> fauna.process_turn(dt)          # Lotka-Volterra, migration, emergence
        -> feedback.process_turn(dt)       # flora<->fauna bidirectional effects
        -> competition.process_turn(dt)    # niche competition, tier advancement
        -> settlements.process_turn(dt)    # lair/camp emergence, abandonment
        -> ecosystem.compute_tiers()       # recompute tile tiers
        -> biome_classifier.update()       # recompute biome_id where flora changed
  5. Victory check
  6. Autosave

Age of Dwarves Constraints

  • Mundane fauna only — no undead, elementals, golems
  • No enchanted_forest biome (Nature magic biome)
  • No mana fields on terrain data
  • No magical events (events/magical.json dormant)
  • Magic engine code stays dormant (not deleted)
  • NPC settlements: lairs (from fauna) + dwarven barbarian camps only
  • No city-state evolution or NPC diplomacy (expansion feature)
  • No free people havens (expansion feature — requires multiple races)
  • Victory: Domination + Score only
  • Game pack: games/age-of-dwarves/