# Theriapolis — Procedural World Generation
### Addendum A: Border Organics & Linear Feature Tile Rules

---

## ISSUE 1: Organic Borders — No Straight Lines

### The Problem

Biome-to-biome boundaries and coastline edges render as straight lines when biome
assignment is done per-cell without edge treatment. A grid of cells, each tagged with
a biome, produces staircase patterns at best and ruler-straight borders at worst.
Neither looks natural.

### The Rule

**No border between any two biome types, and no border between land and ocean, may
contain a straight segment longer than 2 tiles.**

"Straight segment" is defined as: 3 or more consecutive border-edge tiles that form
a line along any cardinal or diagonal axis without deviation.

This rule applies to:
- Biome-to-biome transitions (forest ↔ grassland, grassland ↔ mountain, etc.)
- Land-to-ocean transitions (coastline)
- Land-to-lake transitions
- Land-to-river transitions (riverbanks)

---

### Implementation: Border Noise Injection

Biome borders are NOT the raw edges of macro-cell assignments. They are post-processed
using a dedicated border-shaping pass.

**Step 1 — Identify Raw Borders**

After biome assignment (Layer 1), scan the tile grid for every tile that is adjacent
(including diagonal) to a tile of a different biome type or a water tile. Tag these
tiles as BORDER tiles.

**Step 2 — Generate Border Distortion Map**

Create a separate high-frequency Simplex noise layer (distinct from the terrain noise).
This is the **Border Noise Layer**. Parameters:

```
frequency:   high (4×–8× the base terrain noise frequency)
octaves:     2–3
amplitude:   scaled to 1–3 tile displacement
seed:        world_seed + BORDER_OFFSET (constant offset to decouple from terrain)
```

The purpose of this layer: it tells each BORDER tile how far and in what direction to
"push" the border from its mathematically derived position.

**Step 3 — Apply Distortion to Border Tiles**

For each BORDER tile:

1. Sample the Border Noise Layer at the tile's coordinates. This yields a displacement
   vector (dx, dy), each component ranging from -3 to +3 tiles.
2. The tile's biome assignment is re-evaluated: instead of asking "what biome does
   THIS cell belong to?", ask "what biome does the cell at (x + dx, y + dy) belong to?"
3. This effectively warps the border, creating irregular, organic-looking transitions.

The result: borders between biomes become wavy, irregular curves with peninsulas of
one biome jutting into another, concavities, and natural-looking interlock patterns.

**Step 4 — Coastline-Specific Treatment**

Coastlines receive an ADDITIONAL distortion pass with higher amplitude (2–5 tile
displacement) and lower frequency (producing broad bays and headlands rather than
jagged noise). This is layered on top of the standard border distortion.

Coastline distortion parameters:

```
frequency:   2×–4× base terrain noise
octaves:     3–4 (more detail than biome borders)
amplitude:   2–5 tile displacement
persistence: 0.5 (each octave contributes less — smooth large shapes with fine detail)
```

This produces coastlines with large-scale features (bays, peninsulas, capes) and
small-scale features (coves, rocky outcrops, sandbars) simultaneously.

**Step 5 — Validation Pass**

After distortion, run a straight-line detector across all borders:

```
for each BORDER tile B:
    look at the 6 consecutive border tiles in each of the 8 directions
        (N, NE, E, SE, S, SW, W, NW)
    if 3+ consecutive tiles form a collinear segment:
        flag for correction
```

Flagged tiles receive a forced 1-tile perpendicular displacement (randomly left or
right of the border line). This is the safety net — it should rarely trigger if the
noise parameters are tuned correctly, but it guarantees the rule is never violated.

---

### Implementation: Biome Transition Zones

Beyond border shape, the visual and mechanical transition between biomes should never
be a hard line. Implement a **transition band** of 2–4 tiles on each side of every
biome border where tiles are assigned a MIXED biome type:

- Forest ↔ Grassland border: transition tiles are FOREST_EDGE (scattered trees,
  tall grass, mixed flora).
- Grassland ↔ Mountain border: transition tiles are FOOTHILLS (rocky grassland,
  increasing elevation, scrub).
- Forest ↔ Wetland border: transition tiles are MARSH_EDGE (muddy ground, standing
  water between trees).
- Any biome ↔ Coastline: transition tiles are BEACH, CLIFF, TIDAL_FLAT, or
  MANGROVE depending on biome type and elevation.

Transition bands are also distorted by the Border Noise Layer, so their width varies
organically (wider in some spots, narrower in others).

**Visual Rule:** A player walking from forest into grassland should cross: dense forest
→ thinning forest → scattered trees with grass → tall grass → open grassland. Never
a single-tile hard switch.

---

### Diagram: Border Distortion Visualization

```
RAW BIOME ASSIGNMENT (bad — straight edges):

FFFFFFGGGGG
FFFFFFGGGGG
FFFFFFGGGGG
FFFFFFGGGGG
FFFFFFGGGGG

AFTER BORDER NOISE (good — organic edges):

FFFFfgGGGGG
FFFFFfGGGGG
FFFfggGGGGG
FFFFfgGGGGG
FFFFFfgGGGG

F = Forest (core)
G = Grassland (core)
f = Forest Edge (transition)
g = Grassland Edge (transition)

COASTLINE EXAMPLE:

Raw:                    After Distortion:

LLLLLOOOOO              LLLLllOOOO
LLLLLOOOOO              LLLLLlOOOO
LLLLLOOOOO              LLLllOOOOO
LLLLLOOOOO              LLLLlOOOOO
LLLLLOOOOO              LLLLLllOOO

L = Land
O = Ocean
l = Coastal transition (beach/cliff/tidal)
```

---

### Additional Organic Features

**Peninsula and Bay Generation:**

Beyond noise distortion, explicitly generate 2–4 large-scale coastal features per
continental edge:

1. **Peninsulas:** Select a random coastline point. Extend a noise-distorted finger
   of land 10–30 tiles into the ocean at a random angle. Width tapers from 5–10 tiles
   at base to 1–3 tiles at tip. Peninsulas are high-value settlement locations (natural
   harbors on the flanks).

2. **Bays:** Select a random coastline point. Carve a noise-distorted concavity 10–20
   tiles into the land. Width varies 5–15 tiles. Bays are natural harbor sites —
   settlement placement scores bay interiors highly.

3. **Islands:** 3–8 islands generated offshore. Each is a small noise-generated
   heightmap (10–30 tiles diameter), placed 5–20 tiles off the nearest coast. Islands
   can host Tier 4–5 settlements, PoIs, or remain wilderness.

These features are placed BEFORE border distortion (they modify the raw land/ocean
mask), ensuring the distortion pass treats them as natural coastline.

**River Meander:**

Rivers generated by drainage simulation (Layer 2) also receive meander treatment:

1. After the base river path is computed (gradient descent), apply a lateral sine-wave
   displacement along the river's length.
2. Amplitude: 1–3 tiles (wider in flat terrain, tighter in mountain valleys).
3. Frequency: varies by river length (longer rivers have broader, slower meanders).
4. In very flat terrain (grasslands, floodplains), meander amplitude increases and
   occasional oxbow lakes are generated (abandoned meander loops that became
   disconnected).

---
---

## ISSUE 2: Linear Feature Tile Exclusivity

### The Problem

Rivers, roads, and railroads are all drawn as tile-based linear features on the world
map. When two or more of these features occupy the same tile and run in the same
direction (parallel), they become visually indistinguishable, cause rendering conflicts,
and create gameplay ambiguity (is the player on the road or in the river?).

### The Rule

**No tile may contain more than one linear feature running in the same direction.**

Specifically:

- A river and a road may NOT occupy the same tile running parallel (same or adjacent
  direction).
- A river and a railroad may NOT occupy the same tile running parallel.
- A road and a railroad may NOT occupy the same tile running parallel.
- A river, road, and railroad may NEVER all occupy the same tile under any circumstances.

**Crossings ARE permitted.** A road may cross a river (bridge tile). A railroad may
cross a river (bridge tile). A road may cross a railroad (crossing tile). These are
perpendicular or near-perpendicular intersections, not parallel co-occupation.

**"Parallel" Definition:**

Two linear features are considered parallel on a tile if their traversal directions
through that tile differ by 45 degrees or less.

```
PERMITTED (crossing — ~90° intersection):

    Road →→→
         ↓
    River ↓↓↓

FORBIDDEN (parallel — same direction):

    Road  →→→→→→
    River →→→→→→    (both heading east through the same tiles)

FORBIDDEN (near-parallel — ~45° on same tile):

    Road  →→↗
    River →→→    (road angling NE while river goes E — too close)

PERMITTED (diverging after cross):

    Road  →→→↗↗↗
    River →→→→→→    (road crosses river then diverges — crossing tile is fine)
```

---

### Implementation: Linear Feature Priority and Exclusion Zones

Linear features are generated in a fixed priority order. Higher-priority features
claim tiles first. Lower-priority features must route around claimed tiles.

**Priority Order:**
1. **Rivers** (highest — water flows where gravity dictates; it was here first)
2. **Railroads** (second — expensive infrastructure, routes are surveyed and committed)
3. **Roads** (third — roads are the most flexible and can divert most easily)

**Step 1 — River Generation (Layer 2, unchanged)**

Rivers are generated first via drainage simulation. Each river tile is tagged with:
- `feature: RIVER`
- `direction: [N, NE, E, SE, S, SW, W, NW]` (flow direction through this tile)
- `exclusion_zone: true`

The **exclusion zone** for a river tile extends to all 8 adjacent tiles. These adjacent
tiles are tagged:

- `river_adjacent: true`
- `river_direction: [inherited from the river tile's direction]`

This means: no other linear feature may enter a river-adjacent tile traveling in the
same direction (±45°) as the river. They CAN enter the tile traveling perpendicular
(crossing).

**Step 2 — Railroad Generation**

Railroads are generated using A* pathfinding between rail-connected settlements (Layer 4).
The pathfinding cost function is modified:

```
cost(tile) = base_terrain_cost
           + (if tile.feature == RIVER: INFINITY)                    // never on river
           + (if tile.river_adjacent AND direction_parallel: INFINITY) // never parallel adjacent
           + (if tile.river_adjacent AND direction_perpendicular: BRIDGE_COST) // cross OK, expensive
```

Where:
- `INFINITY` = impassable (pathfinder routes around)
- `BRIDGE_COST` = high but finite (bridges are expensive but buildable; the pathfinder
  will cross rivers when routing around is worse)
- `direction_parallel` = the railroad's proposed direction through this tile is within
  45° of `river_direction`
- `direction_perpendicular` = the railroad's proposed direction is 60°+ different from
  `river_direction`

When a railroad tile is placed, it receives the same treatment as rivers:
- `feature: RAILROAD`
- `direction: [direction]`
- `exclusion_zone: true`
- Adjacent tiles tagged `railroad_adjacent`, `railroad_direction`

**Step 3 — Road Generation**

Roads are generated last, using the same modified A* pathfinding but now respecting
BOTH river and railroad exclusion zones:

```
cost(tile) = base_terrain_cost
           + (if tile.feature == RIVER: INFINITY)
           + (if tile.feature == RAILROAD: INFINITY)
           + (if tile.river_adjacent AND direction_parallel: INFINITY)
           + (if tile.railroad_adjacent AND direction_parallel: INFINITY)
           + (if tile.river_adjacent AND direction_perpendicular: BRIDGE_COST)
           + (if tile.railroad_adjacent AND direction_perpendicular: CROSSING_COST)
```

Where `CROSSING_COST` is lower than `BRIDGE_COST` (road-rail crossings are cheaper
to build than bridges).

---

### Crossing Tile Types

When a linear feature crosses another, the intersection tile receives a special type
that handles both features:

| Crossing | Tile Type | Visual | Gameplay |
|----------|-----------|--------|----------|
| Road crosses River | BRIDGE | Road surface over water, bridge supports visible | Traversable on road; river continues beneath. Bridge can be a chokepoint. |
| Railroad crosses River | RAIL_BRIDGE | Rail tracks on bridge structure over water | Rail travel continues; river flows beneath. |
| Road crosses Railroad | RAIL_CROSSING | Road surface with rail tracks crossing it, warning markers | Both traversable. Possible train-encounter event at crossing. |
| Road crosses Road | CROSSROADS | Intersection | Standard crossroads — signpost, possible encounter point. |

**Crossing Angle Constraint:**

Crossings must occur at 60°–120° angles (near-perpendicular). If the pathfinder
produces a crossing at a shallower angle, the crossing tile is nudged: the lower-
priority feature's path is locally adjusted to approach the crossing more perpendicularly.

```
BAD CROSSING (too shallow, ~30°):

    River →→→→→→→
    Road    ↗↗↗↗

CORRECTED (road approaches at ~80°):

    River →→→→→→→
    Road     ↑↑↗
             ↑
```

This local adjustment affects only the 2–3 tiles immediately before and after the
crossing point. The rest of the road's path is unchanged.

---

### River-Following Roads: The Setback Rule

In real geography, roads often follow river valleys because valleys are flat and rivers
indicate gentle terrain. The generation system should produce roads that travel NEAR
rivers (in the same valley) without occupying the same tiles.

**The Setback Rule:**

When road pathfinding produces a path that would run parallel to a river (same general
direction, within exclusion zone), the road is pushed to a **setback distance** of
3–5 tiles from the river centerline. The road follows the river valley's general
direction but maintains visual and mechanical separation.

Implementation:

```
// During road A* pathfinding, for tiles near a river:

if (tile.river_adjacent AND proposed_direction is parallel to river):
    // Don't use INFINITY — we WANT the road nearby, just not ON the river
    // Instead, add a moderate cost that pushes it a few tiles away
    cost += SETBACK_COST × (1 / distance_to_river)
    // Closer to river = higher cost, pushing the path outward
    // But the valley terrain is still cheaper than mountains,
    // so the road stays in the valley, just offset
```

Visual result:

```
Terrain Cross-Section (valley):

    Mountains    Road    River    Meadow    Mountains
    /\/\/\      ====    ~~~~     ........   /\/\/\
                  ↑        ↑
            3-5 tiles separation
```

The player sees: a road running through a valley with a river visible 3–5 tiles to
one side. Realistic, readable, no tile conflicts.

---

### Railroad-Following Roads: Same Principle

Railroads and roads frequently follow the same corridors (both seek flat, direct
routes). The same setback rule applies:

- Railroad is placed first (higher priority).
- Road pathfinding applies `SETBACK_COST` near railroad tiles, pushing the road
  2–4 tiles to one side.
- The road and railroad travel the same general corridor but on parallel paths
  with clear tile separation.

Visual result:

```
Top-Down View:

    ====  Railroad
    ....  (2-4 tile gap, terrain visible between)
    ----  Road
```

---

### Edge Case: Convergence Points (Settlements)

All three linear features may converge at settlement tiles. Settlements are special:

- Settlement tiles are multi-layer. A town can have a river running through it, a
  rail station, and roads entering from multiple directions.
- **Within settlement boundaries (the tile cluster that forms the town), the exclusion
  rules are RELAXED.** Rivers, roads, and rail can coexist within a town's footprint
  because the settlement's own infrastructure (bridges, crossings, canal walls, station
  platforms) handles the overlap.
- **The exclusion rules resume at the settlement boundary.** As features exit the town,
  they must separate to compliant distances within 2 tiles.

Implementation:

```
if (tile.is_settlement):
    // All linear feature costs revert to base terrain cost
    // No exclusion zones within settlement boundaries
    // Crossing types still apply (bridges, rail crossings)
else:
    // Full exclusion rules in effect
```

This means: a river, road, and railroad can all enter the same town (because towns
form at resource convergence points), but they must diverge onto separate tile paths
once they leave.

---

### Validation Pass

After all three linear feature layers are generated, run a validation sweep:

```
for each tile T on the map:
    features_on_tile = list of linear features present on T
    if len(features_on_tile) > 1:
        if T.is_settlement:
            PASS (settlements are exempt)
        else:
            for each pair (A, B) in features_on_tile:
                angle_diff = abs(A.direction - B.direction)
                if angle_diff < 60°:
                    FLAG AS VIOLATION
                    // Reroute the lower-priority feature around this tile
                    // using local A* with the violating tile set to INFINITY
```

Rerouting is local (affects 3–5 tiles around the violation). The global path is
not recomputed — only the immediate conflict area is adjusted. This keeps the
validation pass cheap.

**Violation Logging:**

In development builds, log all violations with coordinates, feature types, and
directions. Target: 0 violations after the validation pass. Any logged post-
validation violation indicates a bug in the exclusion cost functions.

---

### Summary: Tile Content Rules (Quick Reference)

| Tile State | River | Railroad | Road | Valid? |
|------------|-------|----------|------|--------|
| Empty | — | — | — | Yes |
| River only | ✓ | — | — | Yes |
| Railroad only | — | ✓ | — | Yes |
| Road only | — | — | ✓ | Yes |
| River + Road parallel | ✓ | — | ✓ | **NO** |
| River + Road crossing | ✓ (beneath) | — | ✓ (bridge) | Yes |
| River + Railroad parallel | ✓ | ✓ | — | **NO** |
| River + Railroad crossing | ✓ (beneath) | ✓ (bridge) | — | Yes |
| Road + Railroad parallel | — | ✓ | ✓ | **NO** |
| Road + Railroad crossing | — | ✓ | ✓ (crossing) | Yes |
| River + Road + Railroad | ✓ | ✓ | ✓ | **NO** (never, outside settlements) |
| Any combination in settlement | ✓ | ✓ | ✓ | Yes (settlement exempt) |

---

*Addendum A, Version 1.0 — Border Organics & Linear Feature Tile Rules*
*Compiled by ENI for LO*
