No description
Find a file
McNieps e93cd7fdf0 Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz
This module existed only as a stub .gitignore commit; everything since has
been sitting uncommitted. Adds the hex/vertex/edge/map dataclasses,
heightmap layer application, road pathfinding, YAML serialization, the viz
CLI, project tooling config, and the initial test suite.

Fixes lint/type issues surfaced by running pre-commit for the first time
(StrEnum, line lengths, exception chaining, Pillow's Image.Resampling.LANCZOS,
an unbound-variable path and a wrong None return type in the viz renderer).

Also tightens .gitignore for __pycache__/.venv/caches and excludes manual
viz PNG output (test_map.png) as a non-source artifact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-22 13:14:17 +02:00
src/world_generation Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
tests Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
.gitignore Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
.pre-commit-config.yaml Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
CLAUDE.md Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
pyproject.toml Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
README.md Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00
uv.lock Commit the world-generation engine: hex model, layer pipeline, pathfinding, viz 2026-09-22 13:14:17 +02:00

World Generation

Procedural world generation package for the Autobattler server. Generates a hex-based map with two layers: a fixed base layer (shipped with client) and a per-run layer (generated server-side each run).

Map Specifications

  • Grid: 100×100 hexes
  • Resolution: 2 km per hex
  • Total area: 200 km × 200 km (Provence-sized)

Data Model

Hex

Field Type Description
center_height float Elevation at hex center
terrain enum Primary terrain type (affects base pathfinding cost + visuals)
tags set Modifier tags (e.g., wisteria, cult_sacred) — stack effects
landmark_id optional Reference to landmark data

Vertex

Each hex has 6 vertices, shared between 3 hexes (~3 height values per hex on average). Allows for cliff representation when adjacent hexes don't share vertex heights.

Field Type Description
height float Elevation at vertex

Edge

Each hex has 6 edges, shared between 2 hexes (~30,000 unique edges total).

Field Type Description
river_id optional Reference to named river (null = no river)
road bool Road presence

Bridge is implicit: river_id AND road on same edge.

Generation Pipeline

Fixed Layer → base_map.yml

Generated once, shipped with game client.

  1. Heightmap layers — Apply topography files (topography_1.yml, topography_2.yml, ...) to set center + vertex heights (mountain ranges, basins)
  2. Cliff layers — Split vertices to create discontinuities for escarpments
  3. River spines — Apply river files (river_auronne.yml, river_suaire.yml, ...) to set edge river_id
  4. Zone overlays — Apply zone files (vepres_delta.yml, core_highlands.yml, ...) to set terrain types
  5. Fixed landmarks — Place cities and named locations by constraint
  6. Serialize → base_map.yml

Per-Run Layer

Generated server-side at run start. Sparse data (~few hundred entries).

  1. Wisteria spread — Tag hexes with wisteria based on zone + randomization
  2. Per-run landmarks — Spawn villages/hamlets randomized within zones
  3. Road generation — Pathfind to connect all landmarks (fixed + per-run); existing roads reduce cost (×0.5) to encourage shared segments
  4. Encounter spawns — Place bandits, caches, cult sites per zone density budgets
  5. Fog state — Initialize fog of war

Pathfinding

The engine supports travel-time computation between hexes:

  • Base cost determined by terrain
  • Tags apply multipliers (e.g., wisteria increases cost)
  • Crossing a river edge without road (bridge) increases cost
  • Roads reduce cost
  • Cliffs may block traversal entirely

File Structure

world_generation/              # This package (engine only)
├── src/
│   └── world_generation/
│       ├── __init__.py
│       ├── model/            # Hex, Vertex, Edge, Map dataclasses
│       ├── layers/           # Layer application logic
│       ├── pathfinding/      # A* and road generation
│       └── serialize/        # YAML I/O
├── tests/
├── pyproject.toml
└── README.md

../data/                       # External data (outside this package)
├── world/
│   ├── topography/           # Heightmap layers
│   ├── rivers/               # River spine definitions
│   ├── zones/                # Zone overlays
│   ├── landmarks/            # Fixed landmark definitions
│   └── base_map.yml          # Generated fixed layer (output)
└── encounters/               # Encounter definitions

Usage

from world_generation import generate_base_map, generate_run_layer
from pathlib import Path

data_dir = Path("../data/world")

# Once: generate and save fixed layer
base_map = generate_base_map(data_dir)
base_map.save(data_dir / "base_map.yml")

# Per run: load base and generate run layer
base_map = Map.load(data_dir / "base_map.yml")
run_layer = generate_run_layer(base_map, seed=run_id)

Dependencies

  • Python 3.11+
  • PyYAML
  • NumPy (for heightmap operations)