mapwright 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mapwright-0.2.0/.github/workflows/ci.yml +27 -0
- mapwright-0.2.0/.github/workflows/publish.yml +43 -0
- mapwright-0.2.0/.gitignore +27 -0
- mapwright-0.2.0/CHANGELOG.md +42 -0
- mapwright-0.2.0/LICENSE +21 -0
- mapwright-0.2.0/NOTICE +28 -0
- mapwright-0.2.0/PKG-INFO +136 -0
- mapwright-0.2.0/README.md +113 -0
- mapwright-0.2.0/pyproject.toml +48 -0
- mapwright-0.2.0/src/mapwright/__init__.py +62 -0
- mapwright-0.2.0/src/mapwright/config.py +154 -0
- mapwright-0.2.0/src/mapwright/dungeon.py +229 -0
- mapwright-0.2.0/src/mapwright/names.py +261 -0
- mapwright-0.2.0/src/mapwright/rng.py +163 -0
- mapwright-0.2.0/src/mapwright/svg_renderer.py +249 -0
- mapwright-0.2.0/src/mapwright/terrain.py +542 -0
- mapwright-0.2.0/tests/test_api_contract.py +106 -0
- mapwright-0.2.0/tests/test_config.py +141 -0
- mapwright-0.2.0/tests/test_dungeon.py +103 -0
- mapwright-0.2.0/tests/test_names.py +109 -0
- mapwright-0.2.0/tests/test_rng.py +103 -0
- mapwright-0.2.0/tests/test_svg_renderer.py +117 -0
- mapwright-0.2.0/tests/test_terrain.py +108 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
strategy:
|
|
12
|
+
fail-fast: false
|
|
13
|
+
matrix:
|
|
14
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@v4
|
|
17
|
+
- uses: actions/setup-python@v5
|
|
18
|
+
with:
|
|
19
|
+
python-version: ${{ matrix.python-version }}
|
|
20
|
+
- name: Install
|
|
21
|
+
run: |
|
|
22
|
+
python -m pip install --upgrade pip
|
|
23
|
+
pip install -e ".[dev]"
|
|
24
|
+
- name: Lint (ruff)
|
|
25
|
+
run: ruff check src tests
|
|
26
|
+
- name: Test (pytest)
|
|
27
|
+
run: pytest -q
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
# Publishes to PyPI via Trusted Publishing (OIDC) when a GitHub Release is
|
|
4
|
+
# published. No API token needed — PyPI verifies this repo + workflow + env.
|
|
5
|
+
# To release: tag (vX.Y.Z) and create a GitHub Release for that tag, e.g.
|
|
6
|
+
# gh release create v0.2.0 --generate-notes
|
|
7
|
+
|
|
8
|
+
on:
|
|
9
|
+
release:
|
|
10
|
+
types: [published]
|
|
11
|
+
workflow_dispatch: # allow manual publish of the current main (version in pyproject)
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.12"
|
|
21
|
+
- name: Build sdist + wheel
|
|
22
|
+
run: |
|
|
23
|
+
python -m pip install --upgrade build
|
|
24
|
+
python -m build
|
|
25
|
+
- uses: actions/upload-artifact@v4
|
|
26
|
+
with:
|
|
27
|
+
name: dist
|
|
28
|
+
path: dist/
|
|
29
|
+
|
|
30
|
+
publish:
|
|
31
|
+
needs: build
|
|
32
|
+
runs-on: ubuntu-latest
|
|
33
|
+
environment: pypi # must match the "Environment name" in PyPI's publisher config
|
|
34
|
+
permissions:
|
|
35
|
+
id-token: write # REQUIRED for Trusted Publishing (OIDC)
|
|
36
|
+
contents: read
|
|
37
|
+
steps:
|
|
38
|
+
- uses: actions/download-artifact@v4
|
|
39
|
+
with:
|
|
40
|
+
name: dist
|
|
41
|
+
path: dist/
|
|
42
|
+
- name: Publish to PyPI
|
|
43
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.egg-info/
|
|
5
|
+
.eggs/
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
|
|
9
|
+
# Virtualenvs
|
|
10
|
+
.venv/
|
|
11
|
+
venv/
|
|
12
|
+
env/
|
|
13
|
+
|
|
14
|
+
# Test / tooling caches
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
htmlcov/
|
|
19
|
+
|
|
20
|
+
# Editors / OS
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
.DS_Store
|
|
24
|
+
|
|
25
|
+
# Generated preview artifacts
|
|
26
|
+
*.preview.svg
|
|
27
|
+
*.preview.png
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to mapwright are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/), and the project aims to follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
**Public API** = the names exported in `mapwright.__all__` (pinned by
|
|
8
|
+
`tests/test_api_contract.py`). While the version is `0.x`, minor versions may
|
|
9
|
+
make breaking changes; these will always be noted here.
|
|
10
|
+
|
|
11
|
+
## [Unreleased]
|
|
12
|
+
|
|
13
|
+
## [0.2.0] — 2026-06-01
|
|
14
|
+
|
|
15
|
+
### Added
|
|
16
|
+
- **Dungeon generation** — `DungeonGenerator` → `Dungeon` (+ `DungeonConfig`, `Rect`):
|
|
17
|
+
BSP space-partitioning rooms (no overlap) connected by a Prim minimum-spanning-tree
|
|
18
|
+
of L-corridors, plus optional loop corridors. Returns rooms, carved corridor cells,
|
|
19
|
+
and a boolean walkable grid; `Dungeon.ascii()` for quick previews. Clean-room from
|
|
20
|
+
Dungeon-Generator (MIT, BSP) and donjuan (CC0, MST connectivity).
|
|
21
|
+
|
|
22
|
+
## [0.1.0] — 2026-06-01
|
|
23
|
+
|
|
24
|
+
Initial release. Domain-neutral procedural fantasy map & world generation.
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
- `SeededRNG` — one-seed determinism with `.derive(label)` sub-streams; unifies
|
|
28
|
+
the stdlib and numpy generators. Reproducible across processes.
|
|
29
|
+
- `NameGenerator` / `MarkovNameGenerator` — order-k Markov place/person names
|
|
30
|
+
over hand-authored culture namebases (`NAMEBASES`); hash-seed independent.
|
|
31
|
+
- `RegionalTerrainGenerator` → `TerrainResult` — Voronoi cells (Lloyd-relaxed),
|
|
32
|
+
Planchon–Darboux depression fill, hydraulic + creep erosion, river tracing,
|
|
33
|
+
latitude/elevation climate, and a Whittaker `Biome` matrix.
|
|
34
|
+
- `WorldMapConfig` — bounded, documented world parameters (sea level, continents,
|
|
35
|
+
climate, mountains, rivers) with `from_dict` clamping, named `PRESETS`, and a
|
|
36
|
+
`json_schema()` contract for host/LLM population.
|
|
37
|
+
- `RegionalSVGRenderer` + `Marker` — shaded-relief (hillshade) SVG: biome
|
|
38
|
+
polygons, coastline, rivers, labelled markers. `compute_cell_polygons` rebuilds
|
|
39
|
+
convex Voronoi polygons via half-plane clipping.
|
|
40
|
+
|
|
41
|
+
[Unreleased]: https://github.com/sligara7/mapwright/compare/v0.1.0...HEAD
|
|
42
|
+
[0.1.0]: https://github.com/sligara7/mapwright/releases/tag/v0.1.0
|
mapwright-0.2.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Anthony Sligar
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
mapwright-0.2.0/NOTICE
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
mapwright
|
|
2
|
+
Copyright (c) 2026 Anthony Sligar
|
|
3
|
+
Licensed under the MIT License (see LICENSE).
|
|
4
|
+
|
|
5
|
+
------------------------------------------------------------------------------
|
|
6
|
+
Acknowledgements
|
|
7
|
+
------------------------------------------------------------------------------
|
|
8
|
+
|
|
9
|
+
mapwright's algorithms were implemented clean-room from the publicly described
|
|
10
|
+
ideas and techniques of the following open-source projects. No source code was
|
|
11
|
+
copied from them; this NOTICE credits the lineage of the techniques.
|
|
12
|
+
|
|
13
|
+
* Azgaar's Fantasy-Map-Generator (MIT License)
|
|
14
|
+
https://github.com/Azgaar/Fantasy-Map-Generator
|
|
15
|
+
Ideas: jittered-grid Voronoi with Lloyd relaxation, flux-accumulation
|
|
16
|
+
rivers, temperature x precipitation -> biome matrix, Markov per-culture
|
|
17
|
+
name generation, single-seed determinism, SVG cartographic rendering.
|
|
18
|
+
|
|
19
|
+
* "Generating fantasy maps" by Martin O'Leary (mewo2), and its C++ implementation
|
|
20
|
+
FantasyMapGenerator by Ryan L. Guy (rlguy) (Zlib License)
|
|
21
|
+
https://mewo2.com/notes/terrain/
|
|
22
|
+
https://github.com/rlguy/FantasyMapGenerator
|
|
23
|
+
Ideas: hydraulic + creep erosion (river * sqrt(flux) * slope + creep *
|
|
24
|
+
slope^2), Planchon-Darboux depression filling for guaranteed drainage,
|
|
25
|
+
slope-normal hillshade ("shaded relief") rendering.
|
|
26
|
+
|
|
27
|
+
The name lists ("namebases") bundled with mapwright are original, hand-authored
|
|
28
|
+
data, not derived from any third-party generator.
|
mapwright-0.2.0/PKG-INFO
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mapwright
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Domain-neutral procedural fantasy map & world generation: Voronoi terrain, hydraulic erosion, biomes, rivers, Markov place-names, and shaded-relief SVG.
|
|
5
|
+
Project-URL: Homepage, https://github.com/sligara7/mapwright
|
|
6
|
+
Project-URL: Repository, https://github.com/sligara7/mapwright
|
|
7
|
+
Author: Anthony Sligar
|
|
8
|
+
License: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
License-File: NOTICE
|
|
11
|
+
Keywords: biomes,erosion,fantasy-map,procedural-generation,svg,terrain,ttrpg,voronoi,worldgen
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
16
|
+
Classifier: Topic :: Multimedia :: Graphics
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: numpy>=1.26
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
21
|
+
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# mapwright
|
|
25
|
+
|
|
26
|
+
> ⚠️ **Early development (v0.1, alpha).** The API is still moving and may change without
|
|
27
|
+
> notice between versions. Extracted from a working application; usable today, but pin a
|
|
28
|
+
> commit if you depend on it.
|
|
29
|
+
|
|
30
|
+
**Domain-neutral procedural fantasy map & world generation** — Voronoi terrain with
|
|
31
|
+
hydraulic erosion, climate-driven biomes, rivers, Markov place-names, and shaded-relief
|
|
32
|
+
SVG rendering. Pure Python, `numpy`-only, fully seed-deterministic.
|
|
33
|
+
|
|
34
|
+
mapwright produces *neutral data* (cells, biomes, rivers, polygons) and a self-contained
|
|
35
|
+
SVG renderer. It has no opinion about your application's models — map its output onto your
|
|
36
|
+
own tiles/entities however you like.
|
|
37
|
+
|
|
38
|
+
## Install
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
pip install git+https://github.com/sligara7/mapwright.git
|
|
42
|
+
# or, for local development:
|
|
43
|
+
pip install -e ".[dev]"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Quickstart
|
|
47
|
+
|
|
48
|
+
```python
|
|
49
|
+
from mapwright import SeededRNG, RegionalTerrainGenerator, RegionalSVGRenderer, Marker
|
|
50
|
+
|
|
51
|
+
# Same seed -> same world, every time.
|
|
52
|
+
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(width=60, height=40)
|
|
53
|
+
|
|
54
|
+
markers = [Marker(name="Eldmoor", x=30, y=18, kind="settlement_city")]
|
|
55
|
+
svg = RegionalSVGRenderer().render(terrain, markers)
|
|
56
|
+
open("world.svg", "w").write(svg)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Shape the world with `WorldMapConfig` — or describe it and let an LLM fill the config:
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
from mapwright import WorldMapConfig, RegionalTerrainGenerator, SeededRNG
|
|
63
|
+
|
|
64
|
+
desert = WorldMapConfig.preset("desert") # ready-made worlds...
|
|
65
|
+
custom = WorldMapConfig(continents=7, sea_level=0.55, temperature=-0.8) # ...or tune
|
|
66
|
+
world = RegionalTerrainGenerator(SeededRNG(1)).generate(60, 40, config=desert)
|
|
67
|
+
|
|
68
|
+
# Every field is a bounded scalar with a clear meaning, so it doubles as a schema
|
|
69
|
+
# a host app (or an LLM) can populate. from_dict clamps junk to valid ranges:
|
|
70
|
+
WorldMapConfig.from_dict({"temperature": 5, "continents": -3}) # -> safe, clamped
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Presets: `continent`, `pangaea`, `archipelago`, `islands`, `highlands`, `desert`,
|
|
74
|
+
`arctic`, `tropical`.
|
|
75
|
+
|
|
76
|
+
Procedural place-names in several culture styles:
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from mapwright import SeededRNG, NameGenerator
|
|
80
|
+
|
|
81
|
+
namer = NameGenerator(SeededRNG(7))
|
|
82
|
+
namer.settlement("nordic") # -> 'Eirmundheim'
|
|
83
|
+
namer.settlement("elvish") # -> 'Faelynnwood'
|
|
84
|
+
namer.region("dwarvish") # -> 'The Korvald Reach'
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## What's inside
|
|
88
|
+
|
|
89
|
+
| Component | What it does |
|
|
90
|
+
|-----------|--------------|
|
|
91
|
+
| `SeededRNG` | One seed drives everything; `.derive(label)` yields independent, reproducible sub-streams (unifies stdlib + numpy). |
|
|
92
|
+
| `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
|
|
93
|
+
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) → heightmap → Planchon–Darboux depression fill → flux + hydraulic/creep erosion → rivers → latitude/elevation climate → Whittaker biomes. |
|
|
94
|
+
| `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
|
|
95
|
+
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, labelled markers. |
|
|
96
|
+
| `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
|
|
97
|
+
|
|
98
|
+
Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
|
|
99
|
+
(each with a `Biome`), and you decide how a `Biome` maps to your world.
|
|
100
|
+
|
|
101
|
+
## Determinism
|
|
102
|
+
|
|
103
|
+
Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
|
|
104
|
+
identical world — terrain, names, rivers, and SVG — across runs *and across processes*
|
|
105
|
+
(the Markov chains are built in sorted order, so output never depends on `PYTHONHASHSEED`).
|
|
106
|
+
|
|
107
|
+
## API stability & contract
|
|
108
|
+
|
|
109
|
+
The **public API is exactly the names exported in `mapwright.__all__`** — that's
|
|
110
|
+
the contract. It's pinned by `tests/test_api_contract.py` (public surface, key
|
|
111
|
+
signatures), so an accidental breaking change fails CI.
|
|
112
|
+
|
|
113
|
+
For the world parameters specifically, `WorldMapConfig.json_schema()` returns a
|
|
114
|
+
JSON Schema (draft 2020-12) — the machine-readable contract a host app or LLM can
|
|
115
|
+
validate/generate against, then feed through `WorldMapConfig.from_dict()` (which
|
|
116
|
+
clamps to valid ranges). Schema and runtime clamping are generated from the same
|
|
117
|
+
field spec, so they can't drift.
|
|
118
|
+
|
|
119
|
+
Versioning follows [SemVer](https://semver.org/). While at `0.x` the API may still
|
|
120
|
+
change between minor versions; every change is recorded in `CHANGELOG.md`. Pin a
|
|
121
|
+
tag or commit if you depend on it.
|
|
122
|
+
|
|
123
|
+
## Development
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
python -m venv .venv && . .venv/bin/activate
|
|
127
|
+
pip install -e ".[dev]"
|
|
128
|
+
pytest
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Credits & license
|
|
132
|
+
|
|
133
|
+
MIT licensed (see `LICENSE`). Algorithms were implemented clean-room from the publicly
|
|
134
|
+
described techniques of **Azgaar's Fantasy-Map-Generator** (MIT) and **Martin O'Leary /
|
|
135
|
+
Ryan L. Guy's FantasyMapGenerator** (Zlib); see `NOTICE` for details. The bundled name
|
|
136
|
+
lists are original.
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# mapwright
|
|
2
|
+
|
|
3
|
+
> ⚠️ **Early development (v0.1, alpha).** The API is still moving and may change without
|
|
4
|
+
> notice between versions. Extracted from a working application; usable today, but pin a
|
|
5
|
+
> commit if you depend on it.
|
|
6
|
+
|
|
7
|
+
**Domain-neutral procedural fantasy map & world generation** — Voronoi terrain with
|
|
8
|
+
hydraulic erosion, climate-driven biomes, rivers, Markov place-names, and shaded-relief
|
|
9
|
+
SVG rendering. Pure Python, `numpy`-only, fully seed-deterministic.
|
|
10
|
+
|
|
11
|
+
mapwright produces *neutral data* (cells, biomes, rivers, polygons) and a self-contained
|
|
12
|
+
SVG renderer. It has no opinion about your application's models — map its output onto your
|
|
13
|
+
own tiles/entities however you like.
|
|
14
|
+
|
|
15
|
+
## Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
pip install git+https://github.com/sligara7/mapwright.git
|
|
19
|
+
# or, for local development:
|
|
20
|
+
pip install -e ".[dev]"
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Quickstart
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from mapwright import SeededRNG, RegionalTerrainGenerator, RegionalSVGRenderer, Marker
|
|
27
|
+
|
|
28
|
+
# Same seed -> same world, every time.
|
|
29
|
+
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(width=60, height=40)
|
|
30
|
+
|
|
31
|
+
markers = [Marker(name="Eldmoor", x=30, y=18, kind="settlement_city")]
|
|
32
|
+
svg = RegionalSVGRenderer().render(terrain, markers)
|
|
33
|
+
open("world.svg", "w").write(svg)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Shape the world with `WorldMapConfig` — or describe it and let an LLM fill the config:
|
|
37
|
+
|
|
38
|
+
```python
|
|
39
|
+
from mapwright import WorldMapConfig, RegionalTerrainGenerator, SeededRNG
|
|
40
|
+
|
|
41
|
+
desert = WorldMapConfig.preset("desert") # ready-made worlds...
|
|
42
|
+
custom = WorldMapConfig(continents=7, sea_level=0.55, temperature=-0.8) # ...or tune
|
|
43
|
+
world = RegionalTerrainGenerator(SeededRNG(1)).generate(60, 40, config=desert)
|
|
44
|
+
|
|
45
|
+
# Every field is a bounded scalar with a clear meaning, so it doubles as a schema
|
|
46
|
+
# a host app (or an LLM) can populate. from_dict clamps junk to valid ranges:
|
|
47
|
+
WorldMapConfig.from_dict({"temperature": 5, "continents": -3}) # -> safe, clamped
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Presets: `continent`, `pangaea`, `archipelago`, `islands`, `highlands`, `desert`,
|
|
51
|
+
`arctic`, `tropical`.
|
|
52
|
+
|
|
53
|
+
Procedural place-names in several culture styles:
|
|
54
|
+
|
|
55
|
+
```python
|
|
56
|
+
from mapwright import SeededRNG, NameGenerator
|
|
57
|
+
|
|
58
|
+
namer = NameGenerator(SeededRNG(7))
|
|
59
|
+
namer.settlement("nordic") # -> 'Eirmundheim'
|
|
60
|
+
namer.settlement("elvish") # -> 'Faelynnwood'
|
|
61
|
+
namer.region("dwarvish") # -> 'The Korvald Reach'
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## What's inside
|
|
65
|
+
|
|
66
|
+
| Component | What it does |
|
|
67
|
+
|-----------|--------------|
|
|
68
|
+
| `SeededRNG` | One seed drives everything; `.derive(label)` yields independent, reproducible sub-streams (unifies stdlib + numpy). |
|
|
69
|
+
| `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
|
|
70
|
+
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) → heightmap → Planchon–Darboux depression fill → flux + hydraulic/creep erosion → rivers → latitude/elevation climate → Whittaker biomes. |
|
|
71
|
+
| `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
|
|
72
|
+
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, labelled markers. |
|
|
73
|
+
| `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
|
|
74
|
+
|
|
75
|
+
Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
|
|
76
|
+
(each with a `Biome`), and you decide how a `Biome` maps to your world.
|
|
77
|
+
|
|
78
|
+
## Determinism
|
|
79
|
+
|
|
80
|
+
Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
|
|
81
|
+
identical world — terrain, names, rivers, and SVG — across runs *and across processes*
|
|
82
|
+
(the Markov chains are built in sorted order, so output never depends on `PYTHONHASHSEED`).
|
|
83
|
+
|
|
84
|
+
## API stability & contract
|
|
85
|
+
|
|
86
|
+
The **public API is exactly the names exported in `mapwright.__all__`** — that's
|
|
87
|
+
the contract. It's pinned by `tests/test_api_contract.py` (public surface, key
|
|
88
|
+
signatures), so an accidental breaking change fails CI.
|
|
89
|
+
|
|
90
|
+
For the world parameters specifically, `WorldMapConfig.json_schema()` returns a
|
|
91
|
+
JSON Schema (draft 2020-12) — the machine-readable contract a host app or LLM can
|
|
92
|
+
validate/generate against, then feed through `WorldMapConfig.from_dict()` (which
|
|
93
|
+
clamps to valid ranges). Schema and runtime clamping are generated from the same
|
|
94
|
+
field spec, so they can't drift.
|
|
95
|
+
|
|
96
|
+
Versioning follows [SemVer](https://semver.org/). While at `0.x` the API may still
|
|
97
|
+
change between minor versions; every change is recorded in `CHANGELOG.md`. Pin a
|
|
98
|
+
tag or commit if you depend on it.
|
|
99
|
+
|
|
100
|
+
## Development
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
python -m venv .venv && . .venv/bin/activate
|
|
104
|
+
pip install -e ".[dev]"
|
|
105
|
+
pytest
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Credits & license
|
|
109
|
+
|
|
110
|
+
MIT licensed (see `LICENSE`). Algorithms were implemented clean-room from the publicly
|
|
111
|
+
described techniques of **Azgaar's Fantasy-Map-Generator** (MIT) and **Martin O'Leary /
|
|
112
|
+
Ryan L. Guy's FantasyMapGenerator** (Zlib); see `NOTICE` for details. The bundled name
|
|
113
|
+
lists are original.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mapwright"
|
|
7
|
+
version = "0.2.0"
|
|
8
|
+
description = "Domain-neutral procedural fantasy map & world generation: Voronoi terrain, hydraulic erosion, biomes, rivers, Markov place-names, and shaded-relief SVG."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Anthony Sligar" }]
|
|
13
|
+
keywords = [
|
|
14
|
+
"procedural-generation",
|
|
15
|
+
"fantasy-map",
|
|
16
|
+
"worldgen",
|
|
17
|
+
"voronoi",
|
|
18
|
+
"terrain",
|
|
19
|
+
"erosion",
|
|
20
|
+
"biomes",
|
|
21
|
+
"ttrpg",
|
|
22
|
+
"svg",
|
|
23
|
+
]
|
|
24
|
+
classifiers = [
|
|
25
|
+
"Development Status :: 3 - Alpha",
|
|
26
|
+
"License :: OSI Approved :: MIT License",
|
|
27
|
+
"Programming Language :: Python :: 3",
|
|
28
|
+
"Topic :: Multimedia :: Graphics",
|
|
29
|
+
"Topic :: Games/Entertainment :: Role-Playing",
|
|
30
|
+
]
|
|
31
|
+
dependencies = ["numpy>=1.26"]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = ["pytest>=7.4", "ruff>=0.1"]
|
|
35
|
+
|
|
36
|
+
[project.urls]
|
|
37
|
+
Homepage = "https://github.com/sligara7/mapwright"
|
|
38
|
+
Repository = "https://github.com/sligara7/mapwright"
|
|
39
|
+
|
|
40
|
+
[tool.hatch.build.targets.wheel]
|
|
41
|
+
packages = ["src/mapwright"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
testpaths = ["tests"]
|
|
45
|
+
|
|
46
|
+
[tool.ruff]
|
|
47
|
+
line-length = 100
|
|
48
|
+
target-version = "py310"
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
"""mapwright — domain-neutral procedural fantasy map & world generation.
|
|
2
|
+
|
|
3
|
+
A dependency-light (numpy-only) library:
|
|
4
|
+
|
|
5
|
+
* :class:`SeededRNG` — one seed drives everything; ``.derive(label)`` gives
|
|
6
|
+
independent, reproducible sub-streams.
|
|
7
|
+
* :class:`NameGenerator` — order-k Markov place/person names in several culture
|
|
8
|
+
styles, seed-reproducible across processes.
|
|
9
|
+
* :class:`RegionalTerrainGenerator` — Voronoi cells (Lloyd-relaxed) → heightmap
|
|
10
|
+
→ Planchon–Darboux depression fill → flux + hydraulic/creep erosion → rivers
|
|
11
|
+
→ latitude/elevation climate → Whittaker biomes. Returns neutral data
|
|
12
|
+
(:class:`Biome`, :class:`TerrainResult`); mapping it onto a host app's tile
|
|
13
|
+
vocabulary is the caller's job.
|
|
14
|
+
* :class:`RegionalSVGRenderer` — shaded-relief (hillshade) SVG: biome polygons,
|
|
15
|
+
coastline, rivers, labelled :class:`Marker` points.
|
|
16
|
+
|
|
17
|
+
Built clean-room from the published ideas in Azgaar's Fantasy-Map-Generator (MIT)
|
|
18
|
+
and rlguy/Mewo2's FantasyMapGenerator (Zlib). See NOTICE.
|
|
19
|
+
|
|
20
|
+
Quickstart::
|
|
21
|
+
|
|
22
|
+
from mapwright import SeededRNG, RegionalTerrainGenerator, RegionalSVGRenderer
|
|
23
|
+
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(60, 40)
|
|
24
|
+
svg = RegionalSVGRenderer().render(terrain)
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
from .config import WorldMapConfig, PRESETS
|
|
28
|
+
from .dungeon import Dungeon, DungeonConfig, DungeonGenerator, Rect
|
|
29
|
+
from .names import NameGenerator, MarkovNameGenerator, NAMEBASES
|
|
30
|
+
from .rng import SeededRNG
|
|
31
|
+
from .svg_renderer import Marker, RegionalSVGRenderer
|
|
32
|
+
from .terrain import (
|
|
33
|
+
Biome,
|
|
34
|
+
River,
|
|
35
|
+
TerrainCell,
|
|
36
|
+
TerrainResult,
|
|
37
|
+
RegionalTerrainGenerator,
|
|
38
|
+
compute_cell_polygons,
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
__version__ = "0.2.0"
|
|
42
|
+
|
|
43
|
+
__all__ = [
|
|
44
|
+
"SeededRNG",
|
|
45
|
+
"WorldMapConfig",
|
|
46
|
+
"PRESETS",
|
|
47
|
+
"NameGenerator",
|
|
48
|
+
"MarkovNameGenerator",
|
|
49
|
+
"NAMEBASES",
|
|
50
|
+
"Biome",
|
|
51
|
+
"River",
|
|
52
|
+
"TerrainCell",
|
|
53
|
+
"TerrainResult",
|
|
54
|
+
"RegionalTerrainGenerator",
|
|
55
|
+
"compute_cell_polygons",
|
|
56
|
+
"Marker",
|
|
57
|
+
"RegionalSVGRenderer",
|
|
58
|
+
"Dungeon",
|
|
59
|
+
"DungeonConfig",
|
|
60
|
+
"DungeonGenerator",
|
|
61
|
+
"Rect",
|
|
62
|
+
]
|