Home Biomes, Adjacency & Terrain Transitions

Biomes, Adjacency & Terrain Transitions

How Flatland defines, validates, generates, and mutates map surfaces. Terrain remains AABB terrain_zones plus optional runtime cell deltas — not a full on-disk grid.

Related: 37 — Economic map zones, 21 — World content schemas, 23 — Harvest, 15 — Active stats (vitals drains), 06 — Visibility.


5.1 Catalog (the content files)

biomes:
  - id: temperate_plains
    label: Temperate plains
    map_color: "#5a8f4a"   # Map layout biome-zone tint
    preferred_families: [grassland, path, bare_soil, tilled]
    weather_weights:      # biases for weather picker when online
      clear: 0.5
      rain: 0.3
      drought: 0.15
      storm: 0.05
    drain_tags: []         # optional vitals DrainModifier tags

5.2 Segment biome_zones

Same rect / z_order pattern as terrain:

biome_zones:
  - id: starter-temperate
    biome_id: temperate_plains
    z_order: 0
    rects:
      - { x0: 0, y0: 0, x1: 256, y1: 256 }

biome_at(x,y) → catalog entry. Used for adjacency affinity, weather home, drain hints, and growth allowed_biomes checks.


6.1 Rules (the content files)

rules:
  - family: arid
    forbids: [wetland, water]
    allows: [arid, bare_soil, stone, path]
  - family: grassland
    forbids: [arid]          # no desert glued to lush grass
    allows: [grassland, bare_soil, tilled, path, wetland]

Optional: in_biomes: [temperate_plains] to scope a rule. Optional rare tile_id overrides.

6.2 Editor behavior

Mode Forbidden neighbor
Paint (web Map) Soft warn; allow override for hand repair
Generate / Fill Hard reject; backtrack or pick another family
Validate Report edge list for designers

Native editor may later mirror validate; web Map layout is required for generate/validate UX.


7.1 Catalog (the content files)

weather_states: [clear, rain, drought, storm]

transitions:
  - id: grass_to_dirt
    from_family: grassland
    to_family: bare_soil
    to_kind: dirt            # when kind exists; else tile_id
    agent: natural
    base_ticks: 18000        # progress units at mult 1.0
    weather_mult:
      clear: 1.0
      rain: 0.5
      drought: 3.0
      storm: 0.8
    require_growth_zone: false

  - id: cultivate_grass
    from_family: grassland
    to_family: tilled
    to_kind: tilled
    agent: cultivate
    base_ticks: 90           # channel length for intent
    weather_mult:            # all 1.0 until weather ships is fine
      clear: 1.0
      rain: 1.0
      drought: 1.0
      storm: 1.0
    require_growth_zone: true
    require_allowed_kind: true

  - id: fallow_to_grass
    from_family: tilled
    to_family: grassland
    agent: reclaim
    base_ticks: 36000
    weather_mult:
      clear: 1.0
      rain: 1.2
      drought: 0.7
      storm: 1.0
    require_growth_zone: false

7.2 Weather as multiplier (locked)

progress += dt_ticks * catalog.weather_mult[weather_state]
# complete when progress >= base_ticks
  • Natural edges always exist; drought makes grass → dirt faster — it does not fire a one-shot map wipe.
  • Until weather exists, every mult is 1.0 (no schema change when weather lands).
  • Growth respawn uses the same idea: effective_respawn = base / fertility * weather_mult (37).

7.3 Cultivate

  • Intent Cultivate (tool + stamina + facing cell).
  • Requires cell inside a growth zone; kind must be in that zone’s allowed_kinds when the list is non-empty.
  • Applies transition with agent: cultivate immediately (channel), writing a terrain delta — not heartbeat-gated.
  • Does not create property or growth overlays.

8.1 Store

terrain_deltas: HashMap<(i32, i32), TerrainDelta>
TerrainDelta { kind, tile_id?, elevation?, updated_tick }

sample_terrain / gfx: if delta exists for cell, it overrides authored zone for that cell.

Checkpoint with region world state as needed. content editor Publish / bake may coalesce deltas back into terrain_zones YAML when designers want disk canonical.

Later optimization: per-segment bloom — empty bloom ⇒ skip delta lookup.

8.2 Hybrid region heartbeat

Natural transitions must not scan every cell every tick.

  1. Each biome zone (and optionally growth zone / coarse chunk) tracks last_active / last_visited.
  2. Player presence, harvest, combat, or cultivate in/near the zone marks it hot.
  3. Degrade pass (aligned with WorldClock): only hot zones (configurable window — e.g. last N real minutes or M ticks).
  4. Weather state changes (e.g. enter drought) trigger a sweep of hot zones only.
  5. Cold zones: on next player entry, catch up once using elapsed time × applicable weather_mult (current or averaged) so the world changed while away without paying for empty corners.

Settings sketch:

terrain_sim:
  hot_window_ticks: 18000      # e.g. 10 minutes at 30 Hz
  catch_up_on_enter: true
  max_catch_up_transitions: 3  # cap cascading flips per cell per entry

10.1 Web Map layout (primary)

Tool Behavior
Generate Marquee a rect → continue-only (never overwrite existing zones) → grow coherent domains from neighbors + lakes/rivers → Preview → Apply
Semantics Continue-only: never overwrite existing zones (roads or otherwise). Empty cells grow from nearest occupied / outside seed
Water Coherent lakes / rivers on empty cells only; extend existing water seeds when present
Algorithm Multi-source BFS continuation + low-freq domain families × biome weights; adjacency heal; coalesce AABBs
Output Coalesced terrain_zones (same storage as paint); Apply auto-merges the rect
Validate Preview reports healed cell count and remaining adjacency warnings

10.3 Non-goals

Runtime exploration generation / infinite worlds — out of scope for this document.


14. Exit criteria (full foundation)

  • Biome zones load; biome_at used by validate/generate
  • Adjacency forbids desert-family beside wetland/lush grass in generate
  • Transition catalog parses with weather_mult
  • Generate/Fill writes legal coalesced terrain for a masked region
  • Cultivate inside growth writes a delta; outside growth fails
  • Hot/cold heartbeat skips cold natural ticks; entry catch-up applies
  • Weather (when present) only changes rates via catalog mults

15. Example adjacency snippet

# No lush grass glued to desert
rules:
  - family: grassland
    forbids: [arid]
  - family: arid
    forbids: [grassland, wetland, water]
    allows: [arid, bare_soil, stone, path]

With procgen: a temperate biome fill prefers grassland/path; an arid biome fill prefers arid/bare_soil; shared borders use path/bare_soil as legal seams.