522 lines
18 KiB
Markdown
522 lines
18 KiB
Markdown
|
|
# 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*
|