No description
- Python 100%
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> |
||
|---|---|---|
| src/world_generation | ||
| tests | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| CLAUDE.md | ||
| pyproject.toml | ||
| README.md | ||
| uv.lock | ||
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.
- Heightmap layers — Apply topography files (
topography_1.yml,topography_2.yml, ...) to set center + vertex heights (mountain ranges, basins) - Cliff layers — Split vertices to create discontinuities for escarpments
- River spines — Apply river files (
river_auronne.yml,river_suaire.yml, ...) to set edgeriver_id - Zone overlays — Apply zone files (
vepres_delta.yml,core_highlands.yml, ...) to set terrain types - Fixed landmarks — Place cities and named locations by constraint
- Serialize →
base_map.yml
Per-Run Layer
Generated server-side at run start. Sparse data (~few hundred entries).
- Wisteria spread — Tag hexes with
wisteriabased on zone + randomization - Per-run landmarks — Spawn villages/hamlets randomized within zones
- Road generation — Pathfind to connect all landmarks (fixed + per-run); existing roads reduce cost (×0.5) to encourage shared segments
- Encounter spawns — Place bandits, caches, cult sites per zone density budgets
- 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.,
wisteriaincreases 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)