mapwright 0.12.0__tar.gz → 0.17.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.12.0 → mapwright-0.17.0}/.gitignore +1 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/CHANGELOG.md +79 -1
- {mapwright-0.12.0 → mapwright-0.17.0}/NOTICE +20 -1
- {mapwright-0.12.0 → mapwright-0.17.0}/PKG-INFO +124 -5
- {mapwright-0.12.0 → mapwright-0.17.0}/README.md +120 -4
- mapwright-0.17.0/docs/gallery/age-old.png +0 -0
- mapwright-0.17.0/docs/gallery/age-old.svg +1 -0
- mapwright-0.17.0/docs/gallery/age-young.png +0 -0
- mapwright-0.17.0/docs/gallery/age-young.svg +1 -0
- mapwright-0.17.0/docs/gallery/archipelago.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/archipelago.svg +1 -1
- mapwright-0.17.0/docs/gallery/arctic.png +0 -0
- mapwright-0.17.0/docs/gallery/arctic.svg +1 -0
- mapwright-0.17.0/docs/gallery/atlas.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/README.md +17 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/city_castle_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/city_large_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/city_town_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/city_village_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/decoration_compass_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/decoration_creature_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/decoration_ship_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/dune_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/hill_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/hill_2.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/manifest.json +122 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/mountain_mid_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/mountain_old_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/mountain_old_2.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/mountain_young_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/mountain_young_2.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/tree_cactus_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/tree_deciduous_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/tree_deciduous_2.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/tree_pine_1.png +0 -0
- mapwright-0.17.0/docs/gallery/atlas_pack/tree_pine_2.png +0 -0
- mapwright-0.17.0/docs/gallery/continent.png +0 -0
- mapwright-0.17.0/docs/gallery/continent.svg +1 -0
- mapwright-0.17.0/docs/gallery/desert.png +0 -0
- mapwright-0.17.0/docs/gallery/desert.svg +1 -0
- mapwright-0.17.0/docs/gallery/highlands.png +0 -0
- mapwright-0.17.0/docs/gallery/highlands.svg +1 -0
- mapwright-0.17.0/docs/gallery/islands.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/islands.svg +1 -1
- mapwright-0.17.0/docs/gallery/pangaea.png +0 -0
- mapwright-0.17.0/docs/gallery/pangaea.svg +1 -0
- mapwright-0.17.0/docs/gallery/regions.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/regions.svg +1 -1
- mapwright-0.17.0/docs/gallery/roads.png +0 -0
- mapwright-0.17.0/docs/gallery/roads.svg +1 -0
- mapwright-0.17.0/docs/gallery/template-atoll.png +0 -0
- mapwright-0.17.0/docs/gallery/template-atoll.svg +1 -0
- mapwright-0.17.0/docs/gallery/template-isthmus.png +0 -0
- mapwright-0.17.0/docs/gallery/template-isthmus.svg +1 -0
- mapwright-0.17.0/docs/gallery/theme-blueprint.png +0 -0
- mapwright-0.17.0/docs/gallery/theme-blueprint.svg +1 -0
- mapwright-0.17.0/docs/gallery/theme-dune.png +0 -0
- mapwright-0.17.0/docs/gallery/theme-dune.svg +1 -0
- mapwright-0.17.0/docs/gallery/theme-neon.png +0 -0
- mapwright-0.17.0/docs/gallery/theme-neon.svg +1 -0
- mapwright-0.17.0/docs/gallery/theme-parchment.png +0 -0
- mapwright-0.12.0/docs/gallery/roads.svg → mapwright-0.17.0/docs/gallery/theme-parchment.svg +1 -1
- mapwright-0.17.0/docs/gallery/tropical.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/tropical.svg +1 -1
- {mapwright-0.12.0 → mapwright-0.17.0}/examples/gallery.py +82 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/pyproject.toml +5 -2
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/__init__.py +9 -1
- mapwright-0.17.0/src/mapwright/atlas_renderer.py +359 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/config.py +4 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/svg_renderer.py +28 -45
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/terrain.py +281 -73
- mapwright-0.17.0/src/mapwright/themes.py +177 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_api_contract.py +5 -0
- mapwright-0.17.0/tests/test_atlas_renderer.py +163 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_terrain.py +60 -0
- mapwright-0.17.0/tests/test_themes.py +100 -0
- mapwright-0.12.0/docs/gallery/archipelago.png +0 -0
- mapwright-0.12.0/docs/gallery/arctic.png +0 -0
- mapwright-0.12.0/docs/gallery/arctic.svg +0 -1
- mapwright-0.12.0/docs/gallery/continent.png +0 -0
- mapwright-0.12.0/docs/gallery/continent.svg +0 -1
- mapwright-0.12.0/docs/gallery/desert.png +0 -0
- mapwright-0.12.0/docs/gallery/desert.svg +0 -1
- mapwright-0.12.0/docs/gallery/highlands.png +0 -0
- mapwright-0.12.0/docs/gallery/highlands.svg +0 -1
- mapwright-0.12.0/docs/gallery/islands.png +0 -0
- mapwright-0.12.0/docs/gallery/pangaea.png +0 -0
- mapwright-0.12.0/docs/gallery/pangaea.svg +0 -1
- mapwright-0.12.0/docs/gallery/regions.png +0 -0
- mapwright-0.12.0/docs/gallery/roads.png +0 -0
- mapwright-0.12.0/docs/gallery/tropical.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/.github/workflows/ci.yml +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/.github/workflows/publish.yml +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/LICENSE +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/citadel.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/citadel.svg +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/dungeon.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/dungeon.svg +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/port.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/port.svg +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/town.png +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/docs/gallery/town.svg +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/examples/benchmark.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/_geometry.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/_graph.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/_serde.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/dungeon.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/dungeon_renderer.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/names.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/regions.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/rng.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/roads.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/settlement.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/src/mapwright/settlement_renderer.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_config.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_dungeon.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_dungeon_renderer.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_geometry.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_graph.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_names.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_properties.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_regions.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_rng.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_roads.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_serialize.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_settlement.py +0 -0
- {mapwright-0.12.0 → mapwright-0.17.0}/tests/test_svg_renderer.py +0 -0
|
@@ -8,6 +8,81 @@ All notable changes to mapwright are documented here. The format follows
|
|
|
8
8
|
`tests/test_api_contract.py`). While the version is `0.x`, minor versions may
|
|
9
9
|
make breaking changes; these will always be noted here.
|
|
10
10
|
|
|
11
|
+
## [0.17.0] — 2026-06-02
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Render themes — `Theme` + `THEMES`.** `RegionalSVGRenderer` now takes a
|
|
15
|
+
`theme=` (a name or a `Theme`): a palette plus an optional biome *vocabulary*
|
|
16
|
+
that re-skins the same neutral terrain without regenerating anything. The
|
|
17
|
+
`Biome` enum is unchanged — a theme only decides how each biome looks and is
|
|
18
|
+
named — so this is purely additive and the contract is stable. Built-ins:
|
|
19
|
+
`parchment` (default, **byte-identical** to the previous output), `neon`
|
|
20
|
+
(Tron / digital-grid), `dune` (Tatooine / sand medium), and `blueprint`.
|
|
21
|
+
`Theme` is plain hex-string data (JSON-friendly), so a host or image service
|
|
22
|
+
can author new ones; `Theme.biome_label()` exposes the vocabulary (e.g.
|
|
23
|
+
`OCEAN` → "Void"). This is the first slice of the "Dominant Medium" /
|
|
24
|
+
render-theme direction — pair a theme with a matching `ArtPack` for a full
|
|
25
|
+
restyle. Gallery gains a same-continent neon/dune/blueprint showcase.
|
|
26
|
+
|
|
27
|
+
## [0.16.0] — 2026-06-02
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
- **`AtlasRenderer` + `ArtPack` — hand-drawn / themed atlas rendering.** A new
|
|
31
|
+
optional renderer that stamps symbol images from an external *art pack* onto a
|
|
32
|
+
`TerrainResult` to produce a hand-drawn (or any-style) fantasy-map look:
|
|
33
|
+
mountains (young/mid/old by `land_age`), hills, forests (pine/deciduous/cactus
|
|
34
|
+
by climate), dunes, settlements (by marker kind), and sea decorations + a
|
|
35
|
+
compass rose. mapwright ships **no art** — an art pack is a directory of PNG
|
|
36
|
+
symbols plus an optional `manifest.json` that maps mapwright's neutral concepts
|
|
37
|
+
(`Biome`, `land_age`, settlement size) onto art "slots"; a host (e.g. an
|
|
38
|
+
image-generation service) produces packs in any style and this renderer just
|
|
39
|
+
places them. `ArtPack.from_directory()` reads a manifest, or auto-discovers
|
|
40
|
+
slots from a conventional (Nortantis-style) folder layout. Missing fine-grained
|
|
41
|
+
slots fall back to a coarser sibling, so partial packs still work. Requires
|
|
42
|
+
Pillow — install the optional extra: `pip install "mapwright[atlas]"`. The
|
|
43
|
+
core library stays numpy-only; without Pillow, `import mapwright` is unaffected
|
|
44
|
+
and only `AtlasRenderer` rendering raises a clear install hint.
|
|
45
|
+
|
|
46
|
+
## [0.15.0] — 2026-06-02
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
- **`land_age` — geological age of the terrain** (a mapwright-original idea). A new
|
|
50
|
+
`WorldMapConfig` knob: 0 = *young* (jagged, tall, snow-capped peaks — think the
|
|
51
|
+
Rockies), 1 = *old* (worn down to rounded hills and lowlands — the Appalachians).
|
|
52
|
+
It shapes the hypsometric curve (a gamma on land elevation → more/fewer mountains)
|
|
53
|
+
and, for old land, applies weathering passes that smooth the relief. The default
|
|
54
|
+
(0.5) is neutral — terrain is byte-identical to before, so the feature is purely
|
|
55
|
+
opt-in. First slice of a broader age/era/wealth axis (forests, settlements next).
|
|
56
|
+
|
|
57
|
+
## [0.14.0] — 2026-06-02
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
- **Heightmap templates** — an optional, controllable alternative to the default
|
|
61
|
+
tectonic auto-generation. `RegionalTerrainGenerator.generate(..., template=…)`
|
|
62
|
+
builds the heightmap from composable elevation ops (hill, pit, range, trough,
|
|
63
|
+
strait) spread over the cell graph, and `TERRAIN_TEMPLATES` provides named
|
|
64
|
+
continent archetypes: `continents`, `archipelago`, `peninsula`, `isthmus`,
|
|
65
|
+
`volcano`, `atoll`. A template sets the *pattern* of high/low ground; `config`
|
|
66
|
+
still drives sea level (percentile), climate, and rivers on top. Clean-room from
|
|
67
|
+
the documented idea in Azgaar's Fantasy-Map-Generator (see NOTICE). The default
|
|
68
|
+
(no template) tectonic terrain is byte-identical — this is purely additive.
|
|
69
|
+
|
|
70
|
+
## [0.13.0] — 2026-06-02
|
|
71
|
+
|
|
72
|
+
### Changed
|
|
73
|
+
- **Tectonic-plate terrain.** The heightmap is now built from a simple plate
|
|
74
|
+
simulation instead of a radial/noise field: the map is tiled into continental
|
|
75
|
+
and oceanic plates (Voronoi over plate seeds) that drift, and **convergent plate
|
|
76
|
+
boundaries raise mountain ranges** — so continents get organic coastlines *and*
|
|
77
|
+
believable linear mountain belts, with no centre bias. The `continents` knob is
|
|
78
|
+
the number of continental plates; oceanic plates interleave between them, so
|
|
79
|
+
multi-continent worlds (`archipelago`, `islands`) fragment into scattered islands
|
|
80
|
+
around an inner sea rather than one blob. Sea level is now **percentile-based**
|
|
81
|
+
(`sea_level` maps directly to the water fraction). Rivers form reliably across
|
|
82
|
+
all presets (a flux-quantile source threshold). Clean-room from the documented
|
|
83
|
+
ideas of Nortantis (tectonics) and the Fractal Worldmap Generator (percentile
|
|
84
|
+
sea level); see NOTICE. Regenerated the terrain/roads/regions gallery.
|
|
85
|
+
|
|
11
86
|
## [0.12.0] — 2026-06-02
|
|
12
87
|
|
|
13
88
|
### Changed
|
|
@@ -198,7 +273,10 @@ Initial release. Domain-neutral procedural fantasy map & world generation.
|
|
|
198
273
|
polygons, coastline, rivers, labelled markers. `compute_cell_polygons` rebuilds
|
|
199
274
|
convex Voronoi polygons via half-plane clipping.
|
|
200
275
|
|
|
201
|
-
[Unreleased]: https://github.com/sligara7/mapwright/compare/v0.
|
|
276
|
+
[Unreleased]: https://github.com/sligara7/mapwright/compare/v0.15.0...HEAD
|
|
277
|
+
[0.15.0]: https://github.com/sligara7/mapwright/compare/v0.14.0...v0.15.0
|
|
278
|
+
[0.14.0]: https://github.com/sligara7/mapwright/compare/v0.13.0...v0.14.0
|
|
279
|
+
[0.13.0]: https://github.com/sligara7/mapwright/compare/v0.12.0...v0.13.0
|
|
202
280
|
[0.12.0]: https://github.com/sligara7/mapwright/compare/v0.11.0...v0.12.0
|
|
203
281
|
[0.11.0]: https://github.com/sligara7/mapwright/compare/v0.10.0...v0.11.0
|
|
204
282
|
[0.10.0]: https://github.com/sligara7/mapwright/compare/v0.9.0...v0.10.0
|
|
@@ -14,7 +14,9 @@ copied from them; this NOTICE credits the lineage of the techniques.
|
|
|
14
14
|
https://github.com/Azgaar/Fantasy-Map-Generator
|
|
15
15
|
Ideas: jittered-grid Voronoi with Lloyd relaxation, flux-accumulation
|
|
16
16
|
rivers, temperature x precipitation -> biome matrix, Markov per-culture
|
|
17
|
-
name generation, single-seed determinism, SVG cartographic rendering
|
|
17
|
+
name generation, single-seed determinism, SVG cartographic rendering,
|
|
18
|
+
composable heightmap templates (hill/range/pit/trough/strait ops combined
|
|
19
|
+
into named continent archetypes).
|
|
18
20
|
|
|
19
21
|
* "Generating fantasy maps" by Martin O'Leary (mewo2), and its C++ implementation
|
|
20
22
|
FantasyMapGenerator by Ryan L. Guy (rlguy) (Zlib License)
|
|
@@ -31,5 +33,22 @@ copied from them; this NOTICE credits the lineage of the techniques.
|
|
|
31
33
|
subdivided into Voronoi wards. mapwright's settlement code is an independent
|
|
32
34
|
clean-room implementation built on its own geometry primitives.
|
|
33
35
|
|
|
36
|
+
* Nortantis by Joseph Heydorn (AGPLv3)
|
|
37
|
+
https://github.com/jeheydorn/nortantis
|
|
38
|
+
Concept only (NO code derived — AGPLv3 is incompatible with this MIT
|
|
39
|
+
project): the idea of shaping land with a simple tectonic-plate simulation
|
|
40
|
+
(continental/oceanic plates whose convergent boundaries raise mountain
|
|
41
|
+
ranges), and the general approach of compositing a map from stamped symbol
|
|
42
|
+
images. mapwright's plate model and AtlasRenderer are independent clean-room
|
|
43
|
+
implementations; AtlasRenderer's optional art-pack auto-discovery merely
|
|
44
|
+
recognises a Nortantis-like folder naming convention (mountains/, hills/,
|
|
45
|
+
trees/, …) as a data layout — no Nortantis code or art is used or bundled.
|
|
46
|
+
|
|
47
|
+
* "Fractal Worldmap Generator" by John Olsson / Torben Æ. Mogensen
|
|
48
|
+
https://www.lysator.liu.se/~johol/fwmg/fwmg.html
|
|
49
|
+
Ideas (clean-room): a percentile/histogram sea level so a target water
|
|
50
|
+
fraction is met, and elevation built from many random "faults" rather than a
|
|
51
|
+
centred radial field.
|
|
52
|
+
|
|
34
53
|
The name lists ("namebases") bundled with mapwright are original, hand-authored
|
|
35
54
|
data, not derived from any third-party generator.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mapwright
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.17.0
|
|
4
4
|
Summary: Domain-neutral procedural fantasy map & world generation: Voronoi terrain, hydraulic erosion, biomes, rivers, Markov place-names, and shaded-relief SVG.
|
|
5
5
|
Project-URL: Homepage, https://github.com/sligara7/mapwright
|
|
6
6
|
Project-URL: Repository, https://github.com/sligara7/mapwright
|
|
@@ -16,8 +16,11 @@ Classifier: Topic :: Games/Entertainment :: Role-Playing
|
|
|
16
16
|
Classifier: Topic :: Multimedia :: Graphics
|
|
17
17
|
Requires-Python: >=3.10
|
|
18
18
|
Requires-Dist: numpy>=1.26
|
|
19
|
+
Provides-Extra: atlas
|
|
20
|
+
Requires-Dist: pillow>=10; extra == 'atlas'
|
|
19
21
|
Provides-Extra: dev
|
|
20
22
|
Requires-Dist: hypothesis>=6; extra == 'dev'
|
|
23
|
+
Requires-Dist: pillow>=10; extra == 'dev'
|
|
21
24
|
Requires-Dist: pytest>=7.4; extra == 'dev'
|
|
22
25
|
Requires-Dist: ruff>=0.1; extra == 'dev'
|
|
23
26
|
Description-Content-Type: text/markdown
|
|
@@ -38,7 +41,27 @@ own tiles/entities however you like.
|
|
|
38
41
|
|
|
39
42
|
## Gallery
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
**`AtlasRenderer`** — the same neutral terrain, skinned with a hand-drawn *art pack*.
|
|
45
|
+
The art here is original, generated through mapwright's companion image service and
|
|
46
|
+
stamped where the physics put it (mountains on the ranges, forests by climate, sea
|
|
47
|
+
serpents offshore). mapwright itself ships no art — the pack is the skin:
|
|
48
|
+
|
|
49
|
+
<p align="center">
|
|
50
|
+
<img width="640" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/atlas.png" alt="hand-drawn atlas rendered from a sample art pack">
|
|
51
|
+
</p>
|
|
52
|
+
|
|
53
|
+
**Render themes** — the *same* continent (same cells, rivers, roads, settlements),
|
|
54
|
+
re-skinned by swapping a `Theme` (palette + biome vocabulary). No regeneration:
|
|
55
|
+
|
|
56
|
+
<table>
|
|
57
|
+
<tr>
|
|
58
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-neon.png" alt="neon (Tron) theme"><br><sub><code>theme="neon"</code></sub></td>
|
|
59
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-dune.png" alt="dune (sand) theme"><br><sub><code>theme="dune"</code></sub></td>
|
|
60
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-blueprint.png" alt="blueprint theme"><br><sub><code>theme="blueprint"</code></sub></td>
|
|
61
|
+
</tr>
|
|
62
|
+
</table>
|
|
63
|
+
|
|
64
|
+
Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
|
|
42
65
|
produced by [`examples/gallery.py`](examples/gallery.py):
|
|
43
66
|
|
|
44
67
|
<table>
|
|
@@ -65,10 +88,19 @@ produced by [`examples/gallery.py`](examples/gallery.py):
|
|
|
65
88
|
<tr>
|
|
66
89
|
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/roads.png" alt="settlements linked by terrain-routed roads"><br><sub><code>RegionalRoadGenerator</code></sub></td>
|
|
67
90
|
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/regions.png" alt="land partitioned into named territories"><br><sub><code>RegionGenerator</code></sub></td>
|
|
68
|
-
<td></td>
|
|
91
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/template-isthmus.png" alt="isthmus heightmap template"><br><sub><code>template="isthmus"</code></sub></td>
|
|
92
|
+
</tr>
|
|
93
|
+
<tr>
|
|
94
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/template-atoll.png" alt="atoll heightmap template"><br><sub><code>template="atoll"</code></sub></td>
|
|
95
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/age-young.png" alt="young jagged terrain"><br><sub><code>land_age=0</code> (young)</sub></td>
|
|
96
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/age-old.png" alt="old worn terrain"><br><sub><code>land_age=1</code> (old)</sub></td>
|
|
69
97
|
</tr>
|
|
70
98
|
</table>
|
|
71
99
|
|
|
100
|
+
The two right-hand maps above are the **same continent** at `land_age=0` (young, jagged,
|
|
101
|
+
snow-capped peaks) vs `land_age=1` (old, worn down to rounded hills) — a mapwright-original
|
|
102
|
+
"geological age" knob.
|
|
103
|
+
|
|
72
104
|
Regenerate them with `python examples/gallery.py` (SVGs always; PNGs when
|
|
73
105
|
`cairosvg` is installed).
|
|
74
106
|
|
|
@@ -76,6 +108,8 @@ Regenerate them with `python examples/gallery.py` (SVGs always; PNGs when
|
|
|
76
108
|
|
|
77
109
|
```bash
|
|
78
110
|
pip install mapwright
|
|
111
|
+
# hand-drawn / themed atlas rendering (adds Pillow):
|
|
112
|
+
pip install "mapwright[atlas]"
|
|
79
113
|
# latest from git:
|
|
80
114
|
pip install git+https://github.com/sligara7/mapwright.git
|
|
81
115
|
# or, for local development:
|
|
@@ -112,6 +146,18 @@ WorldMapConfig.from_dict({"temperature": 5, "continents": -3}) # -> safe, clamp
|
|
|
112
146
|
Presets: `continent`, `pangaea`, `archipelago`, `islands`, `highlands`, `desert`,
|
|
113
147
|
`arctic`, `tropical`.
|
|
114
148
|
|
|
149
|
+
Terrain defaults to a **tectonic-plate** simulation (organic coasts + mountain ranges).
|
|
150
|
+
For a controllable continent *archetype*, pass a `template` (Azgaar-style composed
|
|
151
|
+
heightmap ops) — `config` still drives sea level, climate, and rivers on top of it:
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from mapwright import RegionalTerrainGenerator, SeededRNG, WorldMapConfig, TERRAIN_TEMPLATES
|
|
155
|
+
|
|
156
|
+
print(list(TERRAIN_TEMPLATES)) # archipelago, volcano, peninsula, isthmus, atoll, continents
|
|
157
|
+
world = RegionalTerrainGenerator(SeededRNG(5)).generate(
|
|
158
|
+
80, 58, WorldMapConfig(sea_level=0.55), template="archipelago")
|
|
159
|
+
```
|
|
160
|
+
|
|
115
161
|
Save and reload worlds (and dungeons) — JSON round-trips losslessly, so a reloaded
|
|
116
162
|
world renders byte-identically:
|
|
117
163
|
|
|
@@ -169,9 +215,11 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
|
|
|
169
215
|
|-----------|--------------|
|
|
170
216
|
| `SeededRNG` | One seed drives everything; `.derive(label)` yields independent, reproducible sub-streams (unifies stdlib + numpy). |
|
|
171
217
|
| `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
|
|
172
|
-
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) →
|
|
218
|
+
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) → **tectonic-plate** heightmap (organic coasts + mountain ranges at plate collisions; percentile sea level) → Planchon–Darboux depression fill → flux + hydraulic/creep erosion → rivers + inland lakes → latitude/elevation climate with **rain-shadow** → Whittaker biomes. |
|
|
173
219
|
| `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
|
|
174
|
-
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. |
|
|
220
|
+
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. Takes a `theme=`. |
|
|
221
|
+
| `Theme` / `THEMES` | A render palette + biome vocabulary; re-skins the same terrain (parchment / neon / dune / blueprint, or your own). The "Dominant Medium" layer. |
|
|
222
|
+
| `AtlasRenderer` / `ArtPack` | Hand-drawn / themed PNG: stamps symbols from an external *art pack* (mountains, forests, hills, settlements, sea decorations) onto the terrain. mapwright ships no art — a pack is a skin. Needs `pip install "mapwright[atlas]"`. |
|
|
175
223
|
| `RegionalRoadGenerator` | Connects settlement sites with trade routes — an MST whose edges are A*-routed over the terrain (avoids sea, climbs/crosses rivers at a cost). |
|
|
176
224
|
| `RegionGenerator` | Partitions land into named factions/territories: spread capitals seed a flood fill over the land graph (sea divides them); each `Region` is Markov-named. |
|
|
177
225
|
| `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
|
|
@@ -182,6 +230,77 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
|
|
|
182
230
|
Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
|
|
183
231
|
(each with a `Biome`), and you decide how a `Biome` maps to your world.
|
|
184
232
|
|
|
233
|
+
## Atlas rendering & art packs
|
|
234
|
+
|
|
235
|
+
`RegionalSVGRenderer` draws a clean shaded-relief map. For a **hand-drawn** (or neon, or
|
|
236
|
+
scrap-metal, or any) look, `AtlasRenderer` stamps little symbol images — mountains, trees,
|
|
237
|
+
hills, towns, sea monsters, a compass — placed exactly where the physics put them.
|
|
238
|
+
|
|
239
|
+
mapwright bundles **no art**. The renderer is the *engine*; the art is a separate **art pack**
|
|
240
|
+
you point it at, so the same world can wear any style without re-generating anything:
|
|
241
|
+
|
|
242
|
+
```python
|
|
243
|
+
from mapwright import SeededRNG, RegionalTerrainGenerator, ArtPack, AtlasRenderer, Marker
|
|
244
|
+
|
|
245
|
+
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(80, 56)
|
|
246
|
+
markers = [Marker("Eldmoor", 40, 28, kind="settlement_castle")]
|
|
247
|
+
|
|
248
|
+
pack = ArtPack.from_directory("path/to/my-art-pack") # needs mapwright[atlas]
|
|
249
|
+
png = AtlasRenderer(pack, scale=12, seed=7).render(terrain, markers, land_age=0.3)
|
|
250
|
+
open("atlas.png", "wb").write(png)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
An **art pack** is just a directory of transparent PNG symbols plus an optional
|
|
254
|
+
`manifest.json` that maps mapwright's neutral concepts onto art **slots**:
|
|
255
|
+
|
|
256
|
+
```jsonc
|
|
257
|
+
{
|
|
258
|
+
"name": "my-pack",
|
|
259
|
+
"colors": {"parchment": "#ecdfbf", "water": "#b5cad1",
|
|
260
|
+
"coast": "#463c2c", "label": "#2b2218"},
|
|
261
|
+
"slots": {
|
|
262
|
+
"mountain.young": {"files": ["mountains/sharp/*.png"], "width": 2.0, "anchor": "bottom"},
|
|
263
|
+
"mountain.old": {"files": ["mountains/eroded/*.png"]},
|
|
264
|
+
"hill": {"files": ["hills/*.png"]},
|
|
265
|
+
"tree.pine": {"files": ["trees/pine/*.png"]},
|
|
266
|
+
"tree.deciduous": {"files": ["trees/leafy/*.png"]},
|
|
267
|
+
"city.castle": {"files": ["cities/castle*.png"]},
|
|
268
|
+
"decoration.compass": {"files": ["compass/*.png"], "anchor": "center"}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Slots the renderer asks for: terrain relief — `mountain.young` / `mountain.mid` /
|
|
274
|
+
`mountain.old` (chosen by `land_age`), `hill`, `tree.pine` / `tree.deciduous` /
|
|
275
|
+
`tree.cactus` (by climate), `dune`; settlements — `city.castle` / `city.large` /
|
|
276
|
+
`city.town` / `city.village` (by marker `kind`); decorations — `decoration.creature`
|
|
277
|
+
/ `decoration.ship` / `decoration.compass`. A missing fine slot falls back to a coarser
|
|
278
|
+
sibling (`mountain.mid` → any `mountain.*`), so partial packs still render. With **no**
|
|
279
|
+
`manifest.json`, `ArtPack.from_directory()` auto-discovers slots from a conventional
|
|
280
|
+
folder layout. Because packs are pure data, a host like an image-generation service can
|
|
281
|
+
**produce them on demand** in any style — the generation stays the same; the pack is the skin.
|
|
282
|
+
|
|
283
|
+
### Render themes
|
|
284
|
+
|
|
285
|
+
The vector `RegionalSVGRenderer` takes a **`Theme`** — a palette plus an optional biome
|
|
286
|
+
*vocabulary* — so the same neutral terrain re-skins into wildly different worlds without
|
|
287
|
+
regenerating anything. The neutral `Biome` enum never changes; a theme just decides how
|
|
288
|
+
each biome looks and is named:
|
|
289
|
+
|
|
290
|
+
```python
|
|
291
|
+
from mapwright import RegionalSVGRenderer, THEMES
|
|
292
|
+
|
|
293
|
+
svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
|
|
294
|
+
# built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
|
|
295
|
+
THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
A `Theme` is plain hex-string data (JSON-friendly), so a host — or the same image service
|
|
299
|
+
that makes art packs — can author new ones. This is the "Dominant Medium" idea from
|
|
300
|
+
mapwright's longer-term vision: a sand planet, a digital grid, and an irradiated waste are
|
|
301
|
+
the *same map* wearing different skins. Pair a theme with a matching `ArtPack` for a full
|
|
302
|
+
restyle of both the vector and hand-drawn renders.
|
|
303
|
+
|
|
185
304
|
## Determinism
|
|
186
305
|
|
|
187
306
|
Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
|
|
@@ -14,7 +14,27 @@ own tiles/entities however you like.
|
|
|
14
14
|
|
|
15
15
|
## Gallery
|
|
16
16
|
|
|
17
|
-
|
|
17
|
+
**`AtlasRenderer`** — the same neutral terrain, skinned with a hand-drawn *art pack*.
|
|
18
|
+
The art here is original, generated through mapwright's companion image service and
|
|
19
|
+
stamped where the physics put it (mountains on the ranges, forests by climate, sea
|
|
20
|
+
serpents offshore). mapwright itself ships no art — the pack is the skin:
|
|
21
|
+
|
|
22
|
+
<p align="center">
|
|
23
|
+
<img width="640" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/atlas.png" alt="hand-drawn atlas rendered from a sample art pack">
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
**Render themes** — the *same* continent (same cells, rivers, roads, settlements),
|
|
27
|
+
re-skinned by swapping a `Theme` (palette + biome vocabulary). No regeneration:
|
|
28
|
+
|
|
29
|
+
<table>
|
|
30
|
+
<tr>
|
|
31
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-neon.png" alt="neon (Tron) theme"><br><sub><code>theme="neon"</code></sub></td>
|
|
32
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-dune.png" alt="dune (sand) theme"><br><sub><code>theme="dune"</code></sub></td>
|
|
33
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-blueprint.png" alt="blueprint theme"><br><sub><code>theme="blueprint"</code></sub></td>
|
|
34
|
+
</tr>
|
|
35
|
+
</table>
|
|
36
|
+
|
|
37
|
+
Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
|
|
18
38
|
produced by [`examples/gallery.py`](examples/gallery.py):
|
|
19
39
|
|
|
20
40
|
<table>
|
|
@@ -41,10 +61,19 @@ produced by [`examples/gallery.py`](examples/gallery.py):
|
|
|
41
61
|
<tr>
|
|
42
62
|
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/roads.png" alt="settlements linked by terrain-routed roads"><br><sub><code>RegionalRoadGenerator</code></sub></td>
|
|
43
63
|
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/regions.png" alt="land partitioned into named territories"><br><sub><code>RegionGenerator</code></sub></td>
|
|
44
|
-
<td></td>
|
|
64
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/template-isthmus.png" alt="isthmus heightmap template"><br><sub><code>template="isthmus"</code></sub></td>
|
|
65
|
+
</tr>
|
|
66
|
+
<tr>
|
|
67
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/template-atoll.png" alt="atoll heightmap template"><br><sub><code>template="atoll"</code></sub></td>
|
|
68
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/age-young.png" alt="young jagged terrain"><br><sub><code>land_age=0</code> (young)</sub></td>
|
|
69
|
+
<td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/age-old.png" alt="old worn terrain"><br><sub><code>land_age=1</code> (old)</sub></td>
|
|
45
70
|
</tr>
|
|
46
71
|
</table>
|
|
47
72
|
|
|
73
|
+
The two right-hand maps above are the **same continent** at `land_age=0` (young, jagged,
|
|
74
|
+
snow-capped peaks) vs `land_age=1` (old, worn down to rounded hills) — a mapwright-original
|
|
75
|
+
"geological age" knob.
|
|
76
|
+
|
|
48
77
|
Regenerate them with `python examples/gallery.py` (SVGs always; PNGs when
|
|
49
78
|
`cairosvg` is installed).
|
|
50
79
|
|
|
@@ -52,6 +81,8 @@ Regenerate them with `python examples/gallery.py` (SVGs always; PNGs when
|
|
|
52
81
|
|
|
53
82
|
```bash
|
|
54
83
|
pip install mapwright
|
|
84
|
+
# hand-drawn / themed atlas rendering (adds Pillow):
|
|
85
|
+
pip install "mapwright[atlas]"
|
|
55
86
|
# latest from git:
|
|
56
87
|
pip install git+https://github.com/sligara7/mapwright.git
|
|
57
88
|
# or, for local development:
|
|
@@ -88,6 +119,18 @@ WorldMapConfig.from_dict({"temperature": 5, "continents": -3}) # -> safe, clamp
|
|
|
88
119
|
Presets: `continent`, `pangaea`, `archipelago`, `islands`, `highlands`, `desert`,
|
|
89
120
|
`arctic`, `tropical`.
|
|
90
121
|
|
|
122
|
+
Terrain defaults to a **tectonic-plate** simulation (organic coasts + mountain ranges).
|
|
123
|
+
For a controllable continent *archetype*, pass a `template` (Azgaar-style composed
|
|
124
|
+
heightmap ops) — `config` still drives sea level, climate, and rivers on top of it:
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
from mapwright import RegionalTerrainGenerator, SeededRNG, WorldMapConfig, TERRAIN_TEMPLATES
|
|
128
|
+
|
|
129
|
+
print(list(TERRAIN_TEMPLATES)) # archipelago, volcano, peninsula, isthmus, atoll, continents
|
|
130
|
+
world = RegionalTerrainGenerator(SeededRNG(5)).generate(
|
|
131
|
+
80, 58, WorldMapConfig(sea_level=0.55), template="archipelago")
|
|
132
|
+
```
|
|
133
|
+
|
|
91
134
|
Save and reload worlds (and dungeons) — JSON round-trips losslessly, so a reloaded
|
|
92
135
|
world renders byte-identically:
|
|
93
136
|
|
|
@@ -145,9 +188,11 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
|
|
|
145
188
|
|-----------|--------------|
|
|
146
189
|
| `SeededRNG` | One seed drives everything; `.derive(label)` yields independent, reproducible sub-streams (unifies stdlib + numpy). |
|
|
147
190
|
| `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
|
|
148
|
-
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) →
|
|
191
|
+
| `RegionalTerrainGenerator` | Voronoi cells (Lloyd-relaxed) → **tectonic-plate** heightmap (organic coasts + mountain ranges at plate collisions; percentile sea level) → Planchon–Darboux depression fill → flux + hydraulic/creep erosion → rivers + inland lakes → latitude/elevation climate with **rain-shadow** → Whittaker biomes. |
|
|
149
192
|
| `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
|
|
150
|
-
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. |
|
|
193
|
+
| `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. Takes a `theme=`. |
|
|
194
|
+
| `Theme` / `THEMES` | A render palette + biome vocabulary; re-skins the same terrain (parchment / neon / dune / blueprint, or your own). The "Dominant Medium" layer. |
|
|
195
|
+
| `AtlasRenderer` / `ArtPack` | Hand-drawn / themed PNG: stamps symbols from an external *art pack* (mountains, forests, hills, settlements, sea decorations) onto the terrain. mapwright ships no art — a pack is a skin. Needs `pip install "mapwright[atlas]"`. |
|
|
151
196
|
| `RegionalRoadGenerator` | Connects settlement sites with trade routes — an MST whose edges are A*-routed over the terrain (avoids sea, climbs/crosses rivers at a cost). |
|
|
152
197
|
| `RegionGenerator` | Partitions land into named factions/territories: spread capitals seed a flood fill over the land graph (sea divides them); each `Region` is Markov-named. |
|
|
153
198
|
| `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
|
|
@@ -158,6 +203,77 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
|
|
|
158
203
|
Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
|
|
159
204
|
(each with a `Biome`), and you decide how a `Biome` maps to your world.
|
|
160
205
|
|
|
206
|
+
## Atlas rendering & art packs
|
|
207
|
+
|
|
208
|
+
`RegionalSVGRenderer` draws a clean shaded-relief map. For a **hand-drawn** (or neon, or
|
|
209
|
+
scrap-metal, or any) look, `AtlasRenderer` stamps little symbol images — mountains, trees,
|
|
210
|
+
hills, towns, sea monsters, a compass — placed exactly where the physics put them.
|
|
211
|
+
|
|
212
|
+
mapwright bundles **no art**. The renderer is the *engine*; the art is a separate **art pack**
|
|
213
|
+
you point it at, so the same world can wear any style without re-generating anything:
|
|
214
|
+
|
|
215
|
+
```python
|
|
216
|
+
from mapwright import SeededRNG, RegionalTerrainGenerator, ArtPack, AtlasRenderer, Marker
|
|
217
|
+
|
|
218
|
+
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(80, 56)
|
|
219
|
+
markers = [Marker("Eldmoor", 40, 28, kind="settlement_castle")]
|
|
220
|
+
|
|
221
|
+
pack = ArtPack.from_directory("path/to/my-art-pack") # needs mapwright[atlas]
|
|
222
|
+
png = AtlasRenderer(pack, scale=12, seed=7).render(terrain, markers, land_age=0.3)
|
|
223
|
+
open("atlas.png", "wb").write(png)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
An **art pack** is just a directory of transparent PNG symbols plus an optional
|
|
227
|
+
`manifest.json` that maps mapwright's neutral concepts onto art **slots**:
|
|
228
|
+
|
|
229
|
+
```jsonc
|
|
230
|
+
{
|
|
231
|
+
"name": "my-pack",
|
|
232
|
+
"colors": {"parchment": "#ecdfbf", "water": "#b5cad1",
|
|
233
|
+
"coast": "#463c2c", "label": "#2b2218"},
|
|
234
|
+
"slots": {
|
|
235
|
+
"mountain.young": {"files": ["mountains/sharp/*.png"], "width": 2.0, "anchor": "bottom"},
|
|
236
|
+
"mountain.old": {"files": ["mountains/eroded/*.png"]},
|
|
237
|
+
"hill": {"files": ["hills/*.png"]},
|
|
238
|
+
"tree.pine": {"files": ["trees/pine/*.png"]},
|
|
239
|
+
"tree.deciduous": {"files": ["trees/leafy/*.png"]},
|
|
240
|
+
"city.castle": {"files": ["cities/castle*.png"]},
|
|
241
|
+
"decoration.compass": {"files": ["compass/*.png"], "anchor": "center"}
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Slots the renderer asks for: terrain relief — `mountain.young` / `mountain.mid` /
|
|
247
|
+
`mountain.old` (chosen by `land_age`), `hill`, `tree.pine` / `tree.deciduous` /
|
|
248
|
+
`tree.cactus` (by climate), `dune`; settlements — `city.castle` / `city.large` /
|
|
249
|
+
`city.town` / `city.village` (by marker `kind`); decorations — `decoration.creature`
|
|
250
|
+
/ `decoration.ship` / `decoration.compass`. A missing fine slot falls back to a coarser
|
|
251
|
+
sibling (`mountain.mid` → any `mountain.*`), so partial packs still render. With **no**
|
|
252
|
+
`manifest.json`, `ArtPack.from_directory()` auto-discovers slots from a conventional
|
|
253
|
+
folder layout. Because packs are pure data, a host like an image-generation service can
|
|
254
|
+
**produce them on demand** in any style — the generation stays the same; the pack is the skin.
|
|
255
|
+
|
|
256
|
+
### Render themes
|
|
257
|
+
|
|
258
|
+
The vector `RegionalSVGRenderer` takes a **`Theme`** — a palette plus an optional biome
|
|
259
|
+
*vocabulary* — so the same neutral terrain re-skins into wildly different worlds without
|
|
260
|
+
regenerating anything. The neutral `Biome` enum never changes; a theme just decides how
|
|
261
|
+
each biome looks and is named:
|
|
262
|
+
|
|
263
|
+
```python
|
|
264
|
+
from mapwright import RegionalSVGRenderer, THEMES
|
|
265
|
+
|
|
266
|
+
svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
|
|
267
|
+
# built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
|
|
268
|
+
THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
A `Theme` is plain hex-string data (JSON-friendly), so a host — or the same image service
|
|
272
|
+
that makes art packs — can author new ones. This is the "Dominant Medium" idea from
|
|
273
|
+
mapwright's longer-term vision: a sand planet, a digital grid, and an irradiated waste are
|
|
274
|
+
the *same map* wearing different skins. Pair a theme with a matching `ArtPack` for a full
|
|
275
|
+
restyle of both the vector and hand-drawn renders.
|
|
276
|
+
|
|
161
277
|
## Determinism
|
|
162
278
|
|
|
163
279
|
Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
|
|
Binary file
|