mapwright 0.16.0__tar.gz → 0.18.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.
Files changed (118) hide show
  1. {mapwright-0.16.0 → mapwright-0.18.0}/CHANGELOG.md +42 -0
  2. {mapwright-0.16.0 → mapwright-0.18.0}/PKG-INFO +62 -5
  3. {mapwright-0.16.0 → mapwright-0.18.0}/README.md +61 -4
  4. mapwright-0.18.0/docs/gallery/atlas.png +0 -0
  5. mapwright-0.18.0/docs/gallery/atlas_pack/README.md +17 -0
  6. mapwright-0.18.0/docs/gallery/atlas_pack/city_castle_1.png +0 -0
  7. mapwright-0.18.0/docs/gallery/atlas_pack/city_large_1.png +0 -0
  8. mapwright-0.18.0/docs/gallery/atlas_pack/city_town_1.png +0 -0
  9. mapwright-0.18.0/docs/gallery/atlas_pack/city_village_1.png +0 -0
  10. mapwright-0.18.0/docs/gallery/atlas_pack/decoration_compass_1.png +0 -0
  11. mapwright-0.18.0/docs/gallery/atlas_pack/decoration_creature_1.png +0 -0
  12. mapwright-0.18.0/docs/gallery/atlas_pack/decoration_ship_1.png +0 -0
  13. mapwright-0.18.0/docs/gallery/atlas_pack/dune_1.png +0 -0
  14. mapwright-0.18.0/docs/gallery/atlas_pack/hill_1.png +0 -0
  15. mapwright-0.18.0/docs/gallery/atlas_pack/hill_2.png +0 -0
  16. mapwright-0.18.0/docs/gallery/atlas_pack/manifest.json +122 -0
  17. mapwright-0.18.0/docs/gallery/atlas_pack/mountain_mid_1.png +0 -0
  18. mapwright-0.18.0/docs/gallery/atlas_pack/mountain_old_1.png +0 -0
  19. mapwright-0.18.0/docs/gallery/atlas_pack/mountain_old_2.png +0 -0
  20. mapwright-0.18.0/docs/gallery/atlas_pack/mountain_young_1.png +0 -0
  21. mapwright-0.18.0/docs/gallery/atlas_pack/mountain_young_2.png +0 -0
  22. mapwright-0.18.0/docs/gallery/atlas_pack/tree_cactus_1.png +0 -0
  23. mapwright-0.18.0/docs/gallery/atlas_pack/tree_deciduous_1.png +0 -0
  24. mapwright-0.18.0/docs/gallery/atlas_pack/tree_deciduous_2.png +0 -0
  25. mapwright-0.18.0/docs/gallery/atlas_pack/tree_pine_1.png +0 -0
  26. mapwright-0.18.0/docs/gallery/atlas_pack/tree_pine_2.png +0 -0
  27. mapwright-0.18.0/docs/gallery/theme-blueprint.png +0 -0
  28. mapwright-0.18.0/docs/gallery/theme-blueprint.svg +1 -0
  29. mapwright-0.18.0/docs/gallery/theme-citadel-neon.png +0 -0
  30. mapwright-0.18.0/docs/gallery/theme-citadel-neon.svg +1 -0
  31. mapwright-0.18.0/docs/gallery/theme-dune.png +0 -0
  32. mapwright-0.18.0/docs/gallery/theme-dune.svg +1 -0
  33. mapwright-0.18.0/docs/gallery/theme-dungeon-blueprint.png +0 -0
  34. mapwright-0.18.0/docs/gallery/theme-dungeon-blueprint.svg +1 -0
  35. mapwright-0.18.0/docs/gallery/theme-neon.png +0 -0
  36. mapwright-0.18.0/docs/gallery/theme-neon.svg +1 -0
  37. mapwright-0.18.0/docs/gallery/theme-parchment.png +0 -0
  38. mapwright-0.18.0/docs/gallery/theme-parchment.svg +1 -0
  39. {mapwright-0.16.0 → mapwright-0.18.0}/examples/gallery.py +73 -4
  40. {mapwright-0.16.0 → mapwright-0.18.0}/pyproject.toml +1 -1
  41. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/__init__.py +12 -1
  42. mapwright-0.18.0/src/mapwright/affordances.py +168 -0
  43. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/dungeon_renderer.py +19 -17
  44. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/settlement_renderer.py +28 -46
  45. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/svg_renderer.py +28 -45
  46. mapwright-0.18.0/src/mapwright/themes.py +291 -0
  47. mapwright-0.18.0/tests/test_affordances.py +158 -0
  48. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_api_contract.py +5 -0
  49. mapwright-0.18.0/tests/test_themes.py +172 -0
  50. {mapwright-0.16.0 → mapwright-0.18.0}/.github/workflows/ci.yml +0 -0
  51. {mapwright-0.16.0 → mapwright-0.18.0}/.github/workflows/publish.yml +0 -0
  52. {mapwright-0.16.0 → mapwright-0.18.0}/.gitignore +0 -0
  53. {mapwright-0.16.0 → mapwright-0.18.0}/LICENSE +0 -0
  54. {mapwright-0.16.0 → mapwright-0.18.0}/NOTICE +0 -0
  55. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/age-old.png +0 -0
  56. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/age-old.svg +0 -0
  57. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/age-young.png +0 -0
  58. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/age-young.svg +0 -0
  59. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/archipelago.png +0 -0
  60. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/archipelago.svg +0 -0
  61. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/arctic.png +0 -0
  62. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/arctic.svg +0 -0
  63. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/citadel.png +0 -0
  64. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/citadel.svg +0 -0
  65. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/continent.png +0 -0
  66. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/continent.svg +0 -0
  67. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/desert.png +0 -0
  68. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/desert.svg +0 -0
  69. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/dungeon.png +0 -0
  70. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/dungeon.svg +0 -0
  71. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/highlands.png +0 -0
  72. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/highlands.svg +0 -0
  73. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/islands.png +0 -0
  74. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/islands.svg +0 -0
  75. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/pangaea.png +0 -0
  76. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/pangaea.svg +0 -0
  77. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/port.png +0 -0
  78. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/port.svg +0 -0
  79. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/regions.png +0 -0
  80. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/regions.svg +0 -0
  81. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/roads.png +0 -0
  82. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/roads.svg +0 -0
  83. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/template-atoll.png +0 -0
  84. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/template-atoll.svg +0 -0
  85. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/template-isthmus.png +0 -0
  86. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/template-isthmus.svg +0 -0
  87. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/town.png +0 -0
  88. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/town.svg +0 -0
  89. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/tropical.png +0 -0
  90. {mapwright-0.16.0 → mapwright-0.18.0}/docs/gallery/tropical.svg +0 -0
  91. {mapwright-0.16.0 → mapwright-0.18.0}/examples/benchmark.py +0 -0
  92. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/_geometry.py +0 -0
  93. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/_graph.py +0 -0
  94. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/_serde.py +0 -0
  95. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/atlas_renderer.py +0 -0
  96. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/config.py +0 -0
  97. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/dungeon.py +0 -0
  98. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/names.py +0 -0
  99. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/regions.py +0 -0
  100. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/rng.py +0 -0
  101. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/roads.py +0 -0
  102. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/settlement.py +0 -0
  103. {mapwright-0.16.0 → mapwright-0.18.0}/src/mapwright/terrain.py +0 -0
  104. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_atlas_renderer.py +0 -0
  105. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_config.py +0 -0
  106. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_dungeon.py +0 -0
  107. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_dungeon_renderer.py +0 -0
  108. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_geometry.py +0 -0
  109. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_graph.py +0 -0
  110. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_names.py +0 -0
  111. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_properties.py +0 -0
  112. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_regions.py +0 -0
  113. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_rng.py +0 -0
  114. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_roads.py +0 -0
  115. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_serialize.py +0 -0
  116. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_settlement.py +0 -0
  117. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_svg_renderer.py +0 -0
  118. {mapwright-0.16.0 → mapwright-0.18.0}/tests/test_terrain.py +0 -0
@@ -8,6 +8,48 @@ 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.18.0] — 2026-06-02
12
+
13
+ ### Added
14
+ - **Environmental affordances + cell aggregation — `affordances` module.** Two
15
+ domain-neutral helpers for turning terrain into a place's *environment*:
16
+ - `environment_affordances(biome, temperature, moisture)` → neutral
17
+ ecology-level tags (`scarce_water`, `disease_vector`, `predator`,
18
+ `extreme_heat`, …). Biome-base tags plus climate-driven additions, so a
19
+ hot+wet forest reads as a steamy jungle. A host app decides what, if
20
+ anything, tags mean mechanically; this library never touches game rules.
21
+ - `summarize_cells(cells)` → `CellSummary` (dominant biome, mean climate,
22
+ hydrology flags, water fraction, affordances) for a footprint / explored
23
+ area / whole map. Deterministic; ties broken by lowest `Biome` value.
24
+ - New exports: `environment_affordances`, `summarize_cells`, `CellSummary`
25
+ (purely additive to `__all__`).
26
+ - **Themes extended to the town & dungeon renderers.** A `Theme` now carries
27
+ nested `SettlementPalette` + `DungeonPalette` sub-palettes, and
28
+ `SettlementSVGRenderer` / `DungeonSVGRenderer` take a `theme=` just like the
29
+ regional renderer — so one theme skins all three. The four built-ins
30
+ (`parchment`, `neon`, `dune`, `blueprint`) now style towns and dungeons
31
+ cohesively (e.g. a neon citadel, a blueprint dungeon). Sub-palettes default to
32
+ parchment, so a theme that only restyles the regional map still drives the
33
+ other renderers, and the default output is **byte-identical**. `SettlementPalette`
34
+ / `DungeonPalette` are importable from `mapwright.themes` for authoring custom
35
+ themes. Gallery gains a themed town + dungeon.
36
+
37
+ ## [0.17.0] — 2026-06-02
38
+
39
+ ### Added
40
+ - **Render themes — `Theme` + `THEMES`.** `RegionalSVGRenderer` now takes a
41
+ `theme=` (a name or a `Theme`): a palette plus an optional biome *vocabulary*
42
+ that re-skins the same neutral terrain without regenerating anything. The
43
+ `Biome` enum is unchanged — a theme only decides how each biome looks and is
44
+ named — so this is purely additive and the contract is stable. Built-ins:
45
+ `parchment` (default, **byte-identical** to the previous output), `neon`
46
+ (Tron / digital-grid), `dune` (Tatooine / sand medium), and `blueprint`.
47
+ `Theme` is plain hex-string data (JSON-friendly), so a host or image service
48
+ can author new ones; `Theme.biome_label()` exposes the vocabulary (e.g.
49
+ `OCEAN` → "Void"). This is the first slice of the "Dominant Medium" /
50
+ render-theme direction — pair a theme with a matching `ArtPack` for a full
51
+ restyle. Gallery gains a same-continent neon/dune/blueprint showcase.
52
+
11
53
  ## [0.16.0] — 2026-06-02
12
54
 
13
55
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapwright
3
- Version: 0.16.0
3
+ Version: 0.18.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
@@ -41,7 +41,36 @@ own tiles/entities however you like.
41
41
 
42
42
  ## Gallery
43
43
 
44
- Every image below is a deterministic render of a built-in preset (or a dungeon),
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
+ The same `theme=` drives the **town and dungeon** renderers too — one skin across all three:
65
+
66
+ <table>
67
+ <tr>
68
+ <td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-citadel-neon.png" alt="neon-themed walled citadel"><br><sub><code>Settlement, theme="neon"</code></sub></td>
69
+ <td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-dungeon-blueprint.png" alt="blueprint-themed dungeon"><br><sub><code>Dungeon, theme="blueprint"</code></sub></td>
70
+ </tr>
71
+ </table>
72
+
73
+ Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
45
74
  produced by [`examples/gallery.py`](examples/gallery.py):
46
75
 
47
76
  <table>
@@ -197,14 +226,16 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
197
226
  | `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
198
227
  | `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. |
199
228
  | `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
200
- | `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. |
229
+ | `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. Takes a `theme=`. |
230
+ | `Theme` / `THEMES` | A render palette + biome vocabulary; re-skins the same terrain — and its towns and dungeons — via one `theme=` (parchment / neon / dune / blueprint, or your own). The "Dominant Medium" layer. |
231
+ | `environment_affordances` / `summarize_cells` | Neutral *ecology* helpers: biome + climate → affordance tags (`scarce_water`, `predator`, …); reduce a set of cells to a `CellSummary` (dominant biome, mean climate, hydrology, affordances). A host decides what tags mean mechanically. |
201
232
  | `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]"`. |
202
233
  | `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). |
203
234
  | `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. |
204
235
  | `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
205
- | `DungeonSVGRenderer` | Renders a `Dungeon` to SVG: walls, carved floor, room outlines, optional tile grid and per-room labels. |
236
+ | `DungeonSVGRenderer` | Renders a `Dungeon` to SVG: walls, carved floor, room outlines, optional tile grid and per-room labels. Takes a `theme=`. |
206
237
  | `SettlementGenerator` | Self-contained town layout: an organic footprint divided into named Voronoi **wards** (market, docks, …), each subdivided into building **lots**, a **street** network (MST over ward adjacency + main roads from gates to the market), an optional defensive **wall** (towers + gate gaps, opened at the harbour when coastal), and optional coastline. |
207
- | `SettlementSVGRenderer` | Renders a `Settlement` to SVG: sea, footprint, kind-coloured wards, building lots, streets, wall with towers/gatehouses, labels. |
238
+ | `SettlementSVGRenderer` | Renders a `Settlement` to SVG: sea, footprint, kind-coloured wards, building lots, streets, wall with towers/gatehouses, labels. Takes a `theme=`. |
208
239
 
209
240
  Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
210
241
  (each with a `Biome`), and you decide how a `Biome` maps to your world.
@@ -259,6 +290,32 @@ sibling (`mountain.mid` → any `mountain.*`), so partial packs still render. Wi
259
290
  folder layout. Because packs are pure data, a host like an image-generation service can
260
291
  **produce them on demand** in any style — the generation stays the same; the pack is the skin.
261
292
 
293
+ ### Render themes
294
+
295
+ The vector `RegionalSVGRenderer` takes a **`Theme`** — a palette plus an optional biome
296
+ *vocabulary* — so the same neutral terrain re-skins into wildly different worlds without
297
+ regenerating anything. The neutral `Biome` enum never changes; a theme just decides how
298
+ each biome looks and is named:
299
+
300
+ ```python
301
+ from mapwright import RegionalSVGRenderer, SettlementSVGRenderer, DungeonSVGRenderer, THEMES
302
+
303
+ svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
304
+ town = SettlementSVGRenderer(theme="neon").render(settlement) # same theme skins the town
305
+ dgn = DungeonSVGRenderer(theme="blueprint").render(dungeon) # …and the dungeon
306
+ # built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
307
+ THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
308
+ ```
309
+
310
+ All three renderers take the same `theme=`, so one theme skins the world map, its towns,
311
+ and its dungeons together (a `Theme` carries nested `SettlementPalette` + `DungeonPalette`,
312
+ importable from `mapwright.themes` for custom packs). A `Theme` is plain hex-string data
313
+ (JSON-friendly), so a host — or the same image service that makes art packs — can author
314
+ new ones. This is the "Dominant Medium" idea from mapwright's longer-term vision: a sand
315
+ planet, a digital grid, and an irradiated waste are the *same map* wearing different skins.
316
+ Pair a theme with a matching `ArtPack` for a full restyle of both the vector and hand-drawn
317
+ renders.
318
+
262
319
  ## Determinism
263
320
 
264
321
  Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
@@ -14,7 +14,36 @@ own tiles/entities however you like.
14
14
 
15
15
  ## Gallery
16
16
 
17
- Every image below is a deterministic render of a built-in preset (or a dungeon),
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
+ The same `theme=` drives the **town and dungeon** renderers too — one skin across all three:
38
+
39
+ <table>
40
+ <tr>
41
+ <td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-citadel-neon.png" alt="neon-themed walled citadel"><br><sub><code>Settlement, theme="neon"</code></sub></td>
42
+ <td align="center"><img width="240" src="https://raw.githubusercontent.com/sligara7/mapwright/main/docs/gallery/theme-dungeon-blueprint.png" alt="blueprint-themed dungeon"><br><sub><code>Dungeon, theme="blueprint"</code></sub></td>
43
+ </tr>
44
+ </table>
45
+
46
+ Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
18
47
  produced by [`examples/gallery.py`](examples/gallery.py):
19
48
 
20
49
  <table>
@@ -170,14 +199,16 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
170
199
  | `NameGenerator` | Order-k character Markov names over hand-authored culture namebases; reproducible across processes. |
171
200
  | `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. |
172
201
  | `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
173
- | `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. |
202
+ | `RegionalSVGRenderer` | Shaded-relief (hillshade) SVG: biome polygons, coastline, rivers, roads, labelled markers. Takes a `theme=`. |
203
+ | `Theme` / `THEMES` | A render palette + biome vocabulary; re-skins the same terrain — and its towns and dungeons — via one `theme=` (parchment / neon / dune / blueprint, or your own). The "Dominant Medium" layer. |
204
+ | `environment_affordances` / `summarize_cells` | Neutral *ecology* helpers: biome + climate → affordance tags (`scarce_water`, `predator`, …); reduce a set of cells to a `CellSummary` (dominant biome, mean climate, hydrology, affordances). A host decides what tags mean mechanically. |
174
205
  | `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
206
  | `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
207
  | `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
208
  | `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
178
- | `DungeonSVGRenderer` | Renders a `Dungeon` to SVG: walls, carved floor, room outlines, optional tile grid and per-room labels. |
209
+ | `DungeonSVGRenderer` | Renders a `Dungeon` to SVG: walls, carved floor, room outlines, optional tile grid and per-room labels. Takes a `theme=`. |
179
210
  | `SettlementGenerator` | Self-contained town layout: an organic footprint divided into named Voronoi **wards** (market, docks, …), each subdivided into building **lots**, a **street** network (MST over ward adjacency + main roads from gates to the market), an optional defensive **wall** (towers + gate gaps, opened at the harbour when coastal), and optional coastline. |
180
- | `SettlementSVGRenderer` | Renders a `Settlement` to SVG: sea, footprint, kind-coloured wards, building lots, streets, wall with towers/gatehouses, labels. |
211
+ | `SettlementSVGRenderer` | Renders a `Settlement` to SVG: sea, footprint, kind-coloured wards, building lots, streets, wall with towers/gatehouses, labels. Takes a `theme=`. |
181
212
 
182
213
  Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
183
214
  (each with a `Biome`), and you decide how a `Biome` maps to your world.
@@ -232,6 +263,32 @@ sibling (`mountain.mid` → any `mountain.*`), so partial packs still render. Wi
232
263
  folder layout. Because packs are pure data, a host like an image-generation service can
233
264
  **produce them on demand** in any style — the generation stays the same; the pack is the skin.
234
265
 
266
+ ### Render themes
267
+
268
+ The vector `RegionalSVGRenderer` takes a **`Theme`** — a palette plus an optional biome
269
+ *vocabulary* — so the same neutral terrain re-skins into wildly different worlds without
270
+ regenerating anything. The neutral `Biome` enum never changes; a theme just decides how
271
+ each biome looks and is named:
272
+
273
+ ```python
274
+ from mapwright import RegionalSVGRenderer, SettlementSVGRenderer, DungeonSVGRenderer, THEMES
275
+
276
+ svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
277
+ town = SettlementSVGRenderer(theme="neon").render(settlement) # same theme skins the town
278
+ dgn = DungeonSVGRenderer(theme="blueprint").render(dungeon) # …and the dungeon
279
+ # built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
280
+ THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
281
+ ```
282
+
283
+ All three renderers take the same `theme=`, so one theme skins the world map, its towns,
284
+ and its dungeons together (a `Theme` carries nested `SettlementPalette` + `DungeonPalette`,
285
+ importable from `mapwright.themes` for custom packs). A `Theme` is plain hex-string data
286
+ (JSON-friendly), so a host — or the same image service that makes art packs — can author
287
+ new ones. This is the "Dominant Medium" idea from mapwright's longer-term vision: a sand
288
+ planet, a digital grid, and an irradiated waste are the *same map* wearing different skins.
289
+ Pair a theme with a matching `ArtPack` for a full restyle of both the vector and hand-drawn
290
+ renders.
291
+
235
292
  ## Determinism
236
293
 
237
294
  Every generator draws from a `SeededRNG`. The same seed (and parameters) reproduces an
Binary file
@@ -0,0 +1,17 @@
1
+ # Sample art pack (`atlas_pack`)
2
+
3
+ A small **example** art pack for `AtlasRenderer`, used only to render the
4
+ hand-drawn atlas thumbnail in the project README (`docs/gallery/atlas.png`,
5
+ produced by `examples/gallery.py`).
6
+
7
+ - **Provenance:** the symbols were generated by an OpenRouter image model through
8
+ mapwright's companion service (storyflow `media_service`), via
9
+ `scripts/gen_mapwright_pack.py` there. They are original, model-generated art —
10
+ not copied from any third-party asset set.
11
+ - **Scope:** this pack lives under `docs/` and is **not** shipped in the PyPI
12
+ wheel (which packages `src/mapwright/` only). mapwright ships no art; this is a
13
+ demo skin.
14
+ - **Layout:** `manifest.json` maps mapwright's neutral slots
15
+ (`mountain.young`, `tree.pine`, `city.castle`, …) to the PNG variants here.
16
+ See the *Atlas rendering & art packs* section of the top-level README for the
17
+ schema.
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "atlas_pack",
3
+ "style": "antique-ink",
4
+ "colors": {
5
+ "parchment": "#ecdfbf",
6
+ "water": "#a9c2cc",
7
+ "coast": "#463c2c",
8
+ "label": "#2b2218"
9
+ },
10
+ "slots": {
11
+ "mountain.young": {
12
+ "files": [
13
+ "mountain_young_1.png",
14
+ "mountain_young_2.png"
15
+ ],
16
+ "width": 2.0,
17
+ "anchor": "bottom"
18
+ },
19
+ "mountain.mid": {
20
+ "files": [
21
+ "mountain_mid_1.png"
22
+ ],
23
+ "width": 2.0,
24
+ "anchor": "bottom"
25
+ },
26
+ "mountain.old": {
27
+ "files": [
28
+ "mountain_old_1.png",
29
+ "mountain_old_2.png"
30
+ ],
31
+ "width": 2.0,
32
+ "anchor": "bottom"
33
+ },
34
+ "hill": {
35
+ "files": [
36
+ "hill_1.png",
37
+ "hill_2.png"
38
+ ],
39
+ "width": 1.5,
40
+ "anchor": "bottom"
41
+ },
42
+ "tree.pine": {
43
+ "files": [
44
+ "tree_pine_1.png",
45
+ "tree_pine_2.png"
46
+ ],
47
+ "width": 1.0,
48
+ "anchor": "bottom"
49
+ },
50
+ "tree.deciduous": {
51
+ "files": [
52
+ "tree_deciduous_1.png",
53
+ "tree_deciduous_2.png"
54
+ ],
55
+ "width": 1.0,
56
+ "anchor": "bottom"
57
+ },
58
+ "tree.cactus": {
59
+ "files": [
60
+ "tree_cactus_1.png"
61
+ ],
62
+ "width": 1.0,
63
+ "anchor": "bottom"
64
+ },
65
+ "dune": {
66
+ "files": [
67
+ "dune_1.png"
68
+ ],
69
+ "width": 0.9,
70
+ "anchor": "bottom"
71
+ },
72
+ "city.castle": {
73
+ "files": [
74
+ "city_castle_1.png"
75
+ ],
76
+ "width": 1.7,
77
+ "anchor": "bottom"
78
+ },
79
+ "city.large": {
80
+ "files": [
81
+ "city_large_1.png"
82
+ ],
83
+ "width": 1.7,
84
+ "anchor": "bottom"
85
+ },
86
+ "city.town": {
87
+ "files": [
88
+ "city_town_1.png"
89
+ ],
90
+ "width": 1.7,
91
+ "anchor": "bottom"
92
+ },
93
+ "city.village": {
94
+ "files": [
95
+ "city_village_1.png"
96
+ ],
97
+ "width": 1.7,
98
+ "anchor": "bottom"
99
+ },
100
+ "decoration.compass": {
101
+ "files": [
102
+ "decoration_compass_1.png"
103
+ ],
104
+ "width": 3.0,
105
+ "anchor": "center"
106
+ },
107
+ "decoration.ship": {
108
+ "files": [
109
+ "decoration_ship_1.png"
110
+ ],
111
+ "width": 3.0,
112
+ "anchor": "center"
113
+ },
114
+ "decoration.creature": {
115
+ "files": [
116
+ "decoration_creature_1.png"
117
+ ],
118
+ "width": 3.0,
119
+ "anchor": "center"
120
+ }
121
+ }
122
+ }