mapwright 0.17.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.17.0 → mapwright-0.18.0}/CHANGELOG.md +26 -0
  2. {mapwright-0.17.0 → mapwright-0.18.0}/PKG-INFO +26 -11
  3. {mapwright-0.17.0 → mapwright-0.18.0}/README.md +25 -10
  4. mapwright-0.18.0/docs/gallery/theme-citadel-neon.png +0 -0
  5. mapwright-0.18.0/docs/gallery/theme-citadel-neon.svg +1 -0
  6. mapwright-0.18.0/docs/gallery/theme-dungeon-blueprint.png +0 -0
  7. mapwright-0.18.0/docs/gallery/theme-dungeon-blueprint.svg +1 -0
  8. {mapwright-0.17.0 → mapwright-0.18.0}/examples/gallery.py +8 -4
  9. {mapwright-0.17.0 → mapwright-0.18.0}/pyproject.toml +1 -1
  10. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/__init__.py +9 -1
  11. mapwright-0.18.0/src/mapwright/affordances.py +168 -0
  12. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/dungeon_renderer.py +19 -17
  13. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/settlement_renderer.py +28 -46
  14. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/themes.py +114 -0
  15. mapwright-0.18.0/tests/test_affordances.py +158 -0
  16. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_api_contract.py +3 -0
  17. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_themes.py +74 -2
  18. {mapwright-0.17.0 → mapwright-0.18.0}/.github/workflows/ci.yml +0 -0
  19. {mapwright-0.17.0 → mapwright-0.18.0}/.github/workflows/publish.yml +0 -0
  20. {mapwright-0.17.0 → mapwright-0.18.0}/.gitignore +0 -0
  21. {mapwright-0.17.0 → mapwright-0.18.0}/LICENSE +0 -0
  22. {mapwright-0.17.0 → mapwright-0.18.0}/NOTICE +0 -0
  23. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/age-old.png +0 -0
  24. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/age-old.svg +0 -0
  25. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/age-young.png +0 -0
  26. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/age-young.svg +0 -0
  27. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/archipelago.png +0 -0
  28. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/archipelago.svg +0 -0
  29. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/arctic.png +0 -0
  30. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/arctic.svg +0 -0
  31. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas.png +0 -0
  32. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/README.md +0 -0
  33. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/city_castle_1.png +0 -0
  34. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/city_large_1.png +0 -0
  35. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/city_town_1.png +0 -0
  36. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/city_village_1.png +0 -0
  37. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/decoration_compass_1.png +0 -0
  38. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/decoration_creature_1.png +0 -0
  39. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/decoration_ship_1.png +0 -0
  40. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/dune_1.png +0 -0
  41. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/hill_1.png +0 -0
  42. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/hill_2.png +0 -0
  43. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/manifest.json +0 -0
  44. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/mountain_mid_1.png +0 -0
  45. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/mountain_old_1.png +0 -0
  46. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/mountain_old_2.png +0 -0
  47. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/mountain_young_1.png +0 -0
  48. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/mountain_young_2.png +0 -0
  49. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/tree_cactus_1.png +0 -0
  50. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/tree_deciduous_1.png +0 -0
  51. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/tree_deciduous_2.png +0 -0
  52. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/tree_pine_1.png +0 -0
  53. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/atlas_pack/tree_pine_2.png +0 -0
  54. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/citadel.png +0 -0
  55. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/citadel.svg +0 -0
  56. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/continent.png +0 -0
  57. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/continent.svg +0 -0
  58. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/desert.png +0 -0
  59. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/desert.svg +0 -0
  60. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/dungeon.png +0 -0
  61. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/dungeon.svg +0 -0
  62. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/highlands.png +0 -0
  63. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/highlands.svg +0 -0
  64. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/islands.png +0 -0
  65. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/islands.svg +0 -0
  66. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/pangaea.png +0 -0
  67. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/pangaea.svg +0 -0
  68. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/port.png +0 -0
  69. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/port.svg +0 -0
  70. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/regions.png +0 -0
  71. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/regions.svg +0 -0
  72. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/roads.png +0 -0
  73. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/roads.svg +0 -0
  74. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/template-atoll.png +0 -0
  75. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/template-atoll.svg +0 -0
  76. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/template-isthmus.png +0 -0
  77. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/template-isthmus.svg +0 -0
  78. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-blueprint.png +0 -0
  79. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-blueprint.svg +0 -0
  80. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-dune.png +0 -0
  81. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-dune.svg +0 -0
  82. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-neon.png +0 -0
  83. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-neon.svg +0 -0
  84. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-parchment.png +0 -0
  85. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/theme-parchment.svg +0 -0
  86. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/town.png +0 -0
  87. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/town.svg +0 -0
  88. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/tropical.png +0 -0
  89. {mapwright-0.17.0 → mapwright-0.18.0}/docs/gallery/tropical.svg +0 -0
  90. {mapwright-0.17.0 → mapwright-0.18.0}/examples/benchmark.py +0 -0
  91. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/_geometry.py +0 -0
  92. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/_graph.py +0 -0
  93. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/_serde.py +0 -0
  94. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/atlas_renderer.py +0 -0
  95. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/config.py +0 -0
  96. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/dungeon.py +0 -0
  97. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/names.py +0 -0
  98. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/regions.py +0 -0
  99. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/rng.py +0 -0
  100. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/roads.py +0 -0
  101. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/settlement.py +0 -0
  102. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/svg_renderer.py +0 -0
  103. {mapwright-0.17.0 → mapwright-0.18.0}/src/mapwright/terrain.py +0 -0
  104. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_atlas_renderer.py +0 -0
  105. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_config.py +0 -0
  106. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_dungeon.py +0 -0
  107. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_dungeon_renderer.py +0 -0
  108. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_geometry.py +0 -0
  109. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_graph.py +0 -0
  110. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_names.py +0 -0
  111. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_properties.py +0 -0
  112. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_regions.py +0 -0
  113. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_rng.py +0 -0
  114. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_roads.py +0 -0
  115. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_serialize.py +0 -0
  116. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_settlement.py +0 -0
  117. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_svg_renderer.py +0 -0
  118. {mapwright-0.17.0 → mapwright-0.18.0}/tests/test_terrain.py +0 -0
@@ -8,6 +8,32 @@ 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
+
11
37
  ## [0.17.0] — 2026-06-02
12
38
 
13
39
  ### Added
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mapwright
3
- Version: 0.17.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
@@ -61,6 +61,15 @@ re-skinned by swapping a `Theme` (palette + biome vocabulary). No regeneration:
61
61
  </tr>
62
62
  </table>
63
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
+
64
73
  Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
65
74
  produced by [`examples/gallery.py`](examples/gallery.py):
66
75
 
@@ -218,14 +227,15 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
218
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. |
219
228
  | `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
220
229
  | `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. |
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. |
222
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]"`. |
223
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). |
224
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. |
225
235
  | `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
226
- | `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=`. |
227
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. |
228
- | `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=`. |
229
239
 
230
240
  Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
231
241
  (each with a `Biome`), and you decide how a `Biome` maps to your world.
@@ -288,18 +298,23 @@ regenerating anything. The neutral `Biome` enum never changes; a theme just deci
288
298
  each biome looks and is named:
289
299
 
290
300
  ```python
291
- from mapwright import RegionalSVGRenderer, THEMES
301
+ from mapwright import RegionalSVGRenderer, SettlementSVGRenderer, DungeonSVGRenderer, THEMES
292
302
 
293
- svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
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
294
306
  # built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
295
307
  THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
296
308
  ```
297
309
 
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.
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.
303
318
 
304
319
  ## Determinism
305
320
 
@@ -34,6 +34,15 @@ re-skinned by swapping a `Theme` (palette + biome vocabulary). No regeneration:
34
34
  </tr>
35
35
  </table>
36
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
+
37
46
  Below: deterministic shaded-relief renders of each built-in preset (or a dungeon),
38
47
  produced by [`examples/gallery.py`](examples/gallery.py):
39
48
 
@@ -191,14 +200,15 @@ Settlement presets: `hamlet`, `village`, `town`, `city`, `port`, `citadel`.
191
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. |
192
201
  | `compute_cell_polygons` | Reconstructs convex Voronoi polygons (half-plane clipping) for vector rendering. |
193
202
  | `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. |
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. |
195
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]"`. |
196
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). |
197
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. |
198
208
  | `DungeonGenerator` | BSP-partitioned rooms + minimum-spanning-tree corridors → rooms, corridor cells, and a walkable grid (with `Dungeon.ascii()`). |
199
- | `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=`. |
200
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. |
201
- | `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=`. |
202
212
 
203
213
  Everything is neutral: `RegionalTerrainGenerator` returns a `TerrainResult` of `TerrainCell`s
204
214
  (each with a `Biome`), and you decide how a `Biome` maps to your world.
@@ -261,18 +271,23 @@ regenerating anything. The neutral `Biome` enum never changes; a theme just deci
261
271
  each biome looks and is named:
262
272
 
263
273
  ```python
264
- from mapwright import RegionalSVGRenderer, THEMES
274
+ from mapwright import RegionalSVGRenderer, SettlementSVGRenderer, DungeonSVGRenderer, THEMES
265
275
 
266
- svg = RegionalSVGRenderer(theme="neon").render(terrain, markers, roads=roads)
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
267
279
  # built-ins: "parchment" (default), "neon" (Tron/digital-grid), "dune" (sand), "blueprint"
268
280
  THEMES["neon"].biome_label(Biome.OCEAN) # -> "Void" (the vocabulary layer)
269
281
  ```
270
282
 
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.
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.
276
291
 
277
292
  ## Determinism
278
293
 
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="630" height="630" viewBox="0 0 630 630"><rect width="630" height="630" fill="#05060a"/><polygon points="111.4,358.6 115.7,328.0 124.8,297.3 138.4,267.1 156.2,238.1 177.7,210.9 202.5,186.2 230.1,164.5 259.9,146.3 291.1,132.1 323.1,122.4 355.1,117.5 386.1,117.7 415.4,123.0 441.9,133.2 465.1,148.1 484.2,167.3 498.7,190.0 508.5,215.6 513.3,243.4 513.2,272.6 508.4,302.4 499.1,332.2 485.6,361.3 468.3,389.2 447.5,415.5 423.7,439.5 397.2,460.7 368.5,478.8 338.3,493.2 307.0,503.4 275.6,509.0 244.8,509.8 215.4,505.6 188.4,496.4 164.5,482.4 144.4,464.0 128.8,441.7 117.9,416.2 112.1,388.2" fill="#0a1420"/><g stroke="#00e5ff" stroke-width="1" stroke-linejoin="round"><polygon points="130.7,284.2 138.4,267.1 156.2,238.1 177.7,210.9 202.5,186.2 209.4,180.8 240.2,218.5 213.3,295.6 165.6,301.6" fill="#0e2838"/><polygon points="364.5,480.7 338.3,493.2 307.0,503.4 295.9,505.4 287.4,466.5 308.8,441.0 346.6,447.3" fill="#0e2838"/><polygon points="490.3,176.9 498.7,190.0 502.3,199.5 440.6,262.5 415.8,262.8 396.7,244.9 390.9,208.8" fill="#0e2838"/><polygon points="213.3,295.6 240.2,218.5 285.7,231.5 273.8,300.1 230.8,307.9" fill="#1b2a52"/><polygon points="488.2,355.6 485.6,361.3 468.3,389.2 447.5,415.5 427.8,435.4 395.2,403.3 423.3,347.4" fill="#0e2838"/><polygon points="427.8,435.4 423.7,439.5 397.2,460.7 368.5,478.8 364.5,480.7 346.6,447.3 371.0,403.0 395.2,403.3" fill="#10202e"/><polygon points="385.3,200.7 390.9,208.8 396.7,244.9 338.3,270.8 292.9,230.2 333.0,182.0" fill="#0e2233"/><polygon points="230.9,483.1 190.8,454.3 189.8,449.0 192.2,441.3 239.7,423.5 254.5,457.4" fill="#0e2233"/><polygon points="333.6,299.1 367.6,330.1 344.0,375.2 323.1,375.7 297.4,345.7 299.5,317.4" fill="#103a4a"/><polygon points="229.8,507.6 215.4,505.6 188.4,496.4 175.8,489.0 190.8,454.3 230.9,483.1" fill="#0e2233"/><polygon points="346.6,447.3 308.8,441.0 298.0,408.6 323.1,375.7 344.0,375.2 371.0,403.0" fill="#10202e"/><polygon points="338.3,270.8 396.7,244.9 415.8,262.8 402.0,324.2 367.6,330.1 333.6,299.1" fill="#0e2233"/><polygon points="192.2,441.3 189.8,449.0 136.4,452.5 128.8,441.7 117.9,416.2 114.9,401.5 166.5,392.0" fill="#0e2233"/><polygon points="298.0,408.6 308.8,441.0 287.4,466.5 254.5,457.4 239.7,423.5 248.0,400.0" fill="#1b2a52"/><polygon points="416.3,123.3 441.9,133.2 465.1,148.1 484.2,167.3 490.3,176.9 390.9,208.8 385.3,200.7" fill="#10202e"/><polygon points="295.9,505.4 275.6,509.0 244.8,509.8 229.8,507.6 230.9,483.1 254.5,457.4 287.4,466.5" fill="#13314a"/><polygon points="503.6,317.6 499.1,332.2 488.2,355.6 423.3,347.4 402.0,324.2 415.8,262.8 440.6,262.5" fill="#0e2233"/><polygon points="423.3,347.4 395.2,403.3 371.0,403.0 344.0,375.2 367.6,330.1 402.0,324.2" fill="#1b2a52"/><polygon points="209.4,180.8 230.1,164.5 259.9,146.3 291.1,132.1 314.4,125.0 333.0,182.0 292.9,230.2 285.7,231.5 240.2,218.5" fill="#0e2233"/><polygon points="297.4,345.7 323.1,375.7 298.0,408.6 248.0,400.0 237.7,375.9 241.5,367.5" fill="#10202e"/><polygon points="165.6,301.6 213.3,295.6 230.8,307.9 241.5,367.5 237.7,375.9 180.0,376.5" fill="#13314a"/><polygon points="314.4,125.0 323.1,122.4 355.1,117.5 386.1,117.7 415.4,123.0 416.3,123.3 385.3,200.7 333.0,182.0" fill="#0e2233"/><polygon points="230.8,307.9 273.8,300.1 299.5,317.4 297.4,345.7 241.5,367.5" fill="#0e2838"/><polygon points="111.4,358.6 115.7,328.0 124.8,297.3 130.7,284.2 165.6,301.6 180.0,376.5 166.5,392.0 114.9,401.5 112.1,388.2" fill="#0e2838"/><polygon points="502.3,199.5 508.5,215.6 513.3,243.4 513.2,272.6 508.4,302.4 503.6,317.6 440.6,262.5" fill="#10202e"/><polygon points="273.8,300.1 285.7,231.5 292.9,230.2 338.3,270.8 333.6,299.1 299.5,317.4" fill="#0e2233"/><polygon points="237.7,375.9 248.0,400.0 239.7,423.5 192.2,441.3 166.5,392.0 180.0,376.5" fill="#0e2838"/><polygon points="189.8,449.0 190.8,454.3 175.8,489.0 164.5,482.4 144.4,464.0 136.4,452.5" fill="#0e2838"/></g><g fill="#0a3a4a" stroke="#00e5ff" stroke-width="0.5" stroke-linejoin="round"><polygon points="227.9,245.3 226.1,250.3 204.5,242.8 206.2,237.8"/><polygon points="234.4,227.4 229.9,240.3 221.7,237.4 226.2,224.5"/><polygon points="216.8,235.6 207.9,232.5 212.3,219.8 221.2,222.9"/><polygon points="220.0,198.0 228.1,207.9 218.1,216.1 214.1,214.7"/><polygon points="231.6,211.4 237.7,218.9 236.3,223.0 223.1,218.3"/><polygon points="196.3,196.9 204.6,188.6 208.9,185.2 215.5,193.2 212.2,202.4"/><polygon points="206.6,206.0 211.6,207.7 207.3,220.1 202.3,218.4"/><polygon points="192.1,201.5 202.0,205.0 198.1,216.3 188.1,212.8"/><polygon points="205.2,224.3 199.3,241.2 193.3,239.1 199.2,222.2"/><polygon points="188.2,237.1 180.8,234.5 186.5,218.0 194.0,220.6"/><polygon points="166.9,229.5 180.0,212.9 183.3,209.6 175.3,232.4"/><polygon points="177.9,239.7 185.3,242.3 178.9,260.7 171.5,258.1"/><polygon points="157.5,240.6 158.2,239.5 162.7,233.7 173.0,237.3 170.3,245.1"/><polygon points="151.0,251.4 155.0,245.0 168.5,249.7 166.1,256.7"/><polygon points="169.2,262.8 177.8,265.9 174.6,275.2 165.9,272.2"/><polygon points="143.9,263.7 148.5,256.3 164.1,261.8 161.3,269.8"/><polygon points="156.2,274.2 172.3,279.8 169.7,287.3 152.6,281.4"/><polygon points="168.1,292.6 165.9,298.9 148.0,290.0 149.9,286.2"/><polygon points="134.6,282.8 140.9,268.9 150.6,272.3 143.2,287.1"/><polygon points="212.3,289.3 211.1,292.8 193.7,295.0 192.1,282.3"/><polygon points="182.8,297.9 170.1,299.5 172.0,294.1"/><polygon points="187.8,295.0 174.7,290.4 179.2,277.6 185.9,279.9"/><polygon points="190.7,243.8 196.6,245.8 190.6,263.0 184.7,261.0"/><polygon points="201.6,247.6 208.0,249.9 202.0,266.9 195.7,264.7"/><polygon points="200.2,272.0 198.0,278.3 180.8,272.3 183.0,266.0"/><polygon points="217.9,274.5 214.5,284.1 203.0,280.1 206.4,270.4"/><polygon points="213.2,252.0 224.0,255.8 219.4,269.0 208.6,265.3"/><polygon points="307.5,500.1 306.3,500.5 298.2,501.9 294.7,485.9 303.9,483.9"/><polygon points="317.3,497.6 312.6,499.1 308.9,482.1 313.7,481.1"/><polygon points="293.6,479.9 291.6,471.0 309.8,467.1 311.8,475.9"/><polygon points="328.5,494.2 321.5,496.5 319.3,486.2 326.5,484.7"/><polygon points="346.5,486.6 337.4,490.9 332.7,492.4 330.9,484.1 345.2,481.0"/><polygon points="317.4,474.2 315.5,465.4 329.9,462.3 331.8,471.1"/><polygon points="318.4,482.3 317.6,479.0 333.6,475.5 334.3,478.9"/><polygon points="334.9,460.9 340.6,459.7 344.2,476.2 338.5,477.4"/><polygon points="360.2,479.3 351.5,483.4 346.2,459.1 349.0,458.5"/><polygon points="314.7,444.7 327.0,446.8 325.2,457.8 317.9,459.4"/><polygon points="332.1,447.4 345.0,449.5 347.0,453.3 330.5,456.9"/><polygon points="293.4,464.3 309.0,445.6 312.2,460.2"/><polygon points="406.1,219.9 412.5,218.9 417.4,234.2 411.3,236.2"/><polygon points="398.7,240.1 395.7,221.7 400.9,220.9 406.3,237.6"/><polygon points="394.8,216.5 393.8,210.5 408.3,205.9 410.9,213.9"/><polygon points="409.3,252.7 401.3,245.1 418.7,239.5 421.7,248.7"/><polygon points="426.5,260.7 416.6,260.8 412.4,256.9 424.1,253.2"/><polygon points="443.0,255.9 439.3,259.6 431.3,259.7 427.8,248.7 439.5,244.9"/><polygon points="426.1,243.1 422.1,230.6 433.7,226.9 437.7,239.4"/><polygon points="460.2,239.3 447.3,252.5 444.7,244.3"/><polygon points="443.3,240.1 438.3,224.6 442.6,223.2 447.6,238.8"/><polygon points="447.4,222.2 455.1,219.7 459.8,234.3 452.1,236.8"/><polygon points="423.1,201.3 433.7,197.9 437.3,209.0 426.7,212.4"/><polygon points="416.6,216.3 412.7,204.0 418.4,202.2 422.3,214.4"/><polygon points="420.1,225.4 418.5,220.6 439.0,214.1 440.5,218.8"/><polygon points="439.4,196.5 446.5,194.2 453.0,214.4 445.8,216.7"/><polygon points="496.3,190.7 496.6,191.1 499.5,198.9 492.0,206.6 486.3,200.9"/><polygon points="489.0,180.6 492.8,186.5 482.8,196.6 472.1,186.0"/><polygon points="475.4,222.6 473.6,224.5 457.7,208.9 469.8,205.1"/><polygon points="488.4,210.0 480.0,218.6 475.0,203.0 479.7,201.5"/><polygon points="470.6,228.7 464.9,234.6 459.6,218.0"/><polygon points="467.5,188.3 477.3,197.8 471.2,199.7"/><polygon points="455.7,203.6 452.0,192.1 463.0,188.6 466.6,200.0"/><polygon points="218.3,294.0 228.9,263.8 240.5,267.9 228.8,301.4"/><polygon points="269.4,296.9 237.0,302.8 242.3,287.4"/><polygon points="248.4,270.4 273.3,279.2 271.6,289.2 245.1,279.9"/><polygon points="276.6,233.2 280.9,234.5 274.6,270.9 264.6,267.4"/><polygon points="240.0,230.7 242.7,223.2 269.0,230.7 265.8,239.7"/><polygon points="231.6,255.9 237.7,238.4 262.8,247.1 256.7,264.6"/><polygon points="419.2,387.5 432.6,394.2 431.6,401.9 417.6,400.1"/><polygon points="400.9,398.1 408.9,382.2 413.9,384.7 412.0,399.5"/><polygon points="411.9,377.1 415.1,370.7 434.1,380.2 433.1,387.7"/><polygon points="419.5,361.9 425.1,350.8 437.6,352.4 436.2,364.0"/><polygon points="419.6,367.1 436.6,369.3 435.8,375.3"/><polygon points="414.7,415.3 423.5,406.4 431.2,407.3 430.0,417.3"/><polygon points="428.6,431.4 427.8,432.2 415.7,420.3 429.7,422.1"/><polygon points="409.3,413.7 398.4,403.0 417.4,405.4"/><polygon points="439.9,421.3 432.5,428.7 433.5,420.5"/><polygon points="452.2,404.8 445.3,413.5 441.7,417.2 435.6,416.4 437.3,402.9"/><polygon points="457.2,398.7 456.5,399.5 438.0,397.2 438.9,390.0 458.0,392.4"/><polygon points="456.8,371.7 461.3,372.3 459.3,387.6 454.9,387.0"/><polygon points="439.6,384.3 441.4,370.6 451.8,371.9 450.1,385.6"/><polygon points="474.6,374.4 466.3,387.8 463.5,391.3 465.8,373.3"/><polygon points="465.0,367.9 453.9,366.5 455.4,354.3 466.6,355.7"/><polygon points="448.8,366.3 441.5,365.3 443.1,352.3 450.4,353.2"/><polygon points="484.3,357.8 483.3,360.0 477.4,369.6 470.4,368.7 471.9,356.2"/><polygon points="356.2,437.4 360.2,430.1 382.1,442.2 378.1,449.5"/><polygon points="364.8,472.6 351.2,447.4 352.9,444.3 374.0,455.9"/><polygon points="400.9,452.8 395.0,457.6 375.3,470.0 388.5,446.0"/><polygon points="391.2,439.1 387.6,437.1 400.6,413.5 408.6,421.4"/><polygon points="422.5,435.4 421.2,436.7 407.4,447.8 397.9,442.5 413.6,426.6"/><polygon points="370.5,411.4 373.1,406.6 393.8,406.8 395.3,408.3 388.3,421.2"/><polygon points="381.6,434.2 363.1,424.0 366.9,417.1 385.4,427.2"/><polygon points="387.8,247.0 378.4,251.1 376.5,246.8 385.9,242.6"/><polygon points="375.2,243.4 372.7,237.6 381.7,233.6 384.2,239.4"/><polygon points="374.9,252.3 370.6,254.2 364.9,241.3 369.2,239.3"/><polygon points="363.0,238.1 360.4,232.3 366.9,229.4 369.5,235.2"/><polygon points="370.0,228.2 377.9,224.7 380.4,230.3 372.5,233.8"/><polygon points="390.6,219.4 392.1,228.7 384.3,229.9 381.4,223.5"/><polygon points="392.8,232.6 394.6,243.7 391.0,245.3 385.9,233.7"/><polygon points="386.7,206.6 388.8,209.6 389.7,215.2 381.4,218.9 377.8,210.6"/><polygon points="384.1,202.2 384.7,203.1 375.8,207.1 371.7,197.8"/><polygon points="353.0,213.9 349.3,205.6 358.4,201.6 362.1,209.9"/><polygon points="362.0,199.6 367.4,197.2 371.4,206.2 365.9,208.6"/><polygon points="378.0,220.9 369.0,224.9 367.0,220.2 376.0,216.2"/><polygon points="365.5,217.3 363.8,213.3 373.0,209.2 374.7,213.2"/><polygon points="365.4,226.1 359.4,228.7 354.6,217.9 360.6,215.3"/><polygon points="346.0,199.2 340.4,186.6 349.3,189.8"/><polygon points="349.3,201.1 352.8,191.2 363.2,194.9"/><polygon points="315.4,220.6 317.3,222.2 310.0,230.9 308.1,229.3"/><polygon points="301.3,223.0 307.9,215.0 312.6,218.9 305.9,226.8"/><polygon points="303.9,237.5 295.5,230.0 298.9,225.9 307.5,233.1"/><polygon points="314.7,230.6 319.5,224.9 327.5,231.6 317.2,236.2"/><polygon points="307.4,241.3 306.0,240.0 312.0,232.8 314.4,238.2"/><polygon points="310.5,212.2 317.4,203.8 324.2,209.4 313.1,214.3"/><polygon points="319.3,200.8 325.1,193.8 328.5,196.7 322.7,203.6"/><polygon points="330.9,198.9 337.0,204.0 328.2,207.9 325.3,205.6"/><polygon points="327.4,191.5 333.6,184.1 335.8,184.9 338.1,190.2 330.1,193.8"/><polygon points="339.6,193.3 343.2,201.4 340.5,202.6 332.9,196.2"/><polygon points="336.0,227.9 331.7,229.8 324.9,224.1 331.0,216.7"/><polygon points="321.8,221.8 316.2,217.1 329.0,211.4 329.5,212.6"/><polygon points="344.5,204.3 348.3,212.9 343.8,214.9 340.0,206.3"/><polygon points="340.8,216.1 335.8,218.3 332.1,209.9 337.0,207.7"/><polygon points="349.4,216.3 351.5,221.0 339.7,226.3 337.6,221.6"/><polygon points="326.3,257.3 321.4,252.9 328.7,244.7 333.0,254.3"/><polygon points="339.6,268.3 338.6,268.8 329.0,260.2 334.8,257.5"/><polygon points="318.3,250.5 310.4,243.4 317.9,240.1 321.1,247.4"/><polygon points="320.6,238.4 325.7,236.2 327.7,240.7 323.6,245.2"/><polygon points="349.0,263.9 342.8,266.6 338.7,257.3 344.8,254.6"/><polygon points="367.3,256.0 363.1,257.9 358.7,248.1 363.0,246.2"/><polygon points="359.5,259.1 352.6,262.2 348.5,253.0 355.5,250.0"/><polygon points="340.5,230.1 344.9,228.2 349.7,239.2 345.4,241.1"/><polygon points="348.3,226.7 353.2,224.6 358.0,235.5 353.2,237.6"/><polygon points="359.5,239.0 361.3,243.0 348.7,248.6 347.0,244.5"/><polygon points="337.0,254.0 334.7,248.9 343.2,245.1 345.5,250.2"/><polygon points="333.2,246.0 331.7,242.7 340.7,238.7 342.2,242.1"/><polygon points="330.6,239.8 328.6,235.2 337.2,231.4 339.2,236.0"/><polygon points="213.4,467.0 207.6,462.9 214.9,452.7 227.0,461.3 227.2,461.9"/><polygon points="216.7,448.3 221.0,442.2 225.8,454.8"/><polygon points="230.6,479.9 218.1,470.9 229.5,466.6 233.3,476.9"/><polygon points="201.7,440.5 217.3,434.6 218.7,438.3 211.9,447.8"/><polygon points="203.0,459.6 193.3,452.7 192.7,449.2 194.5,443.5 196.0,442.9 208.6,451.9"/><polygon points="247.9,448.9 251.4,456.9 245.5,463.4 235.0,453.7"/><polygon points="242.5,467.2 236.9,473.4 230.4,456.1"/><polygon points="221.7,432.7 238.5,426.4 240.0,429.9 223.4,437.2"/><polygon points="241.5,435.0 245.3,443.8 229.0,450.0 225.9,441.8"/><polygon points="181.5,490.0 178.3,488.1 183.3,476.5 189.3,479.1"/><polygon points="185.7,472.6 191.9,458.3 199.6,463.8 191.5,475.1"/><polygon points="204.6,498.9 189.5,493.8 186.1,491.8 191.4,484.5 206.9,495.6"/><polygon points="194.2,480.0 203.8,466.7 208.9,470.3 199.3,483.7"/><polygon points="213.0,473.5 219.5,478.2 210.2,491.2 203.7,486.5"/><polygon points="227.3,504.5 216.0,502.9 210.0,500.9 216.5,491.7 227.5,499.5"/><polygon points="218.8,487.4 223.8,480.5 228.8,484.1 228.3,494.2"/><polygon points="346.8,382.9 354.6,391.0 337.9,407.2 334.4,405.3"/><polygon points="359.3,396.2 366.5,403.6 358.5,418.2 344.6,410.6"/><polygon points="314.7,393.5 325.2,379.7 340.1,379.4 328.2,401.0"/><polygon points="304.5,406.0 309.5,399.4 331.2,411.4 327.3,418.5"/><polygon points="316.1,438.4 311.6,437.7 303.7,413.8 323.6,424.8"/><polygon points="345.0,444.0 322.8,440.2 328.1,430.8 346.6,441.0"/><polygon points="331.8,425.2 337.4,414.9 355.0,424.6 349.3,434.8"/><polygon points="363.9,262.8 373.6,258.5 380.1,273.3 370.4,277.6"/><polygon points="353.7,266.6 358.5,264.4 365.7,280.5 360.8,282.7"/><polygon points="379.1,298.2 370.1,302.2 363.5,287.4 372.5,283.4"/><polygon points="382.9,278.2 389.9,293.9 384.3,296.4 377.4,280.7"/><polygon points="342.9,271.4 349.2,268.6 355.4,282.7 349.2,285.5"/><polygon points="339.6,275.3 345.2,287.7 337.0,291.4"/><polygon points="352.4,310.4 350.7,311.2 336.4,298.1 336.7,296.6 344.7,293.1"/><polygon points="357.2,287.9 364.7,304.7 357.4,307.9 349.9,291.1"/><polygon points="365.0,325.3 353.5,314.8 359.2,312.3"/><polygon points="365.0,315.4 363.1,311.0 377.4,304.6 379.4,309.1"/><polygon points="385.5,324.2 370.7,326.8 367.6,319.7 380.9,313.9"/><polygon points="399.5,314.4 401.2,318.4 400.3,322.4 390.7,324.1 388.6,319.3"/><polygon points="391.8,298.8 396.9,310.3 387.2,314.6 382.1,303.1"/><polygon points="409.7,275.6 406.1,291.9 394.0,289.2 391.5,283.7"/><polygon points="405.7,297.4 402.7,311.0 395.6,295.1"/><polygon points="397.8,275.0 388.6,279.0 384.6,270.1 393.8,266.0"/><polygon points="408.7,259.6 413.0,263.7 412.0,268.5 402.1,272.8 398.3,264.2"/><polygon points="395.0,247.6 396.4,247.0 406.1,256.1 400.0,258.8"/><polygon points="378.8,255.8 391.3,250.3 395.4,259.7 383.0,265.2"/><polygon points="167.1,399.0 175.8,415.8 170.3,418.7 161.6,401.9"/><polygon points="165.7,421.2 154.9,426.8 150.3,417.9 161.1,412.3"/><polygon points="147.6,414.1 145.3,409.6 157.1,403.4 159.4,407.9"/><polygon points="140.4,399.3 163.3,395.1 143.6,405.4"/><polygon points="127.6,401.7 135.4,400.3 141.3,411.7 134.7,415.2"/><polygon points="122.9,421.2 120.4,415.4 118.0,403.6 122.4,402.8 130.1,417.4"/><polygon points="133.9,421.6 143.4,416.7 149.9,429.1 140.4,434.1"/><polygon points="130.5,439.9 124.5,425.9 129.1,423.5 136.1,437.0"/><polygon points="169.0,447.9 160.1,448.5 158.9,430.4 159.6,430.0"/><polygon points="150.9,448.9 137.7,449.8 133.5,443.9 145.1,437.8"/><polygon points="155.8,448.5 148.8,435.1 154.7,432.0"/><polygon points="169.0,436.5 164.6,428.0 178.2,420.8 182.7,429.3"/><polygon points="189.2,441.6 187.7,446.3 174.5,447.2 171.6,441.5 185.4,434.3"/><polygon points="256.2,450.8 244.2,423.4 250.8,404.8 263.8,407.0"/><polygon points="295.5,450.3 285.9,461.8 263.9,455.7 265.7,445.2"/><polygon points="294.8,412.3 304.1,440.1 301.7,442.9 287.0,440.4 291.9,411.8"/><polygon points="278.7,438.9 267.1,437.0 272.0,408.4 283.6,410.4"/><polygon points="463.5,151.8 481.3,169.6 484.7,174.9 472.2,178.9"/><polygon points="464.8,180.9 442.0,188.2 438.5,177.2 461.3,169.9"/><polygon points="435.9,170.1 433.1,161.4 456.5,153.9 459.3,162.5"/><polygon points="423.8,130.7 440.0,137.0 454.5,146.3 431.2,153.8"/><polygon points="408.3,199.1 400.1,173.5 401.0,171.3 416.9,177.7"/><polygon points="433.0,183.6 435.4,191.1 416.5,197.2 423.5,179.8"/><polygon points="402.1,201.9 392.1,205.1 388.8,200.3 395.9,182.5"/><polygon points="416.3,134.6 424.0,158.8 405.6,164.7 404.4,164.2"/><polygon points="427.6,164.7 431.5,176.8 413.0,169.4"/><polygon points="275.2,466.3 284.8,469.0 288.2,484.5 279.5,486.4"/><polygon points="271.6,477.8 273.8,487.8 260.0,490.9 257.8,480.8"/><polygon points="253.9,462.5 255.5,460.8 268.6,464.4 270.3,472.1 256.6,475.1"/><polygon points="235.2,482.3 249.2,467.1 251.7,478.7"/><polygon points="233.4,496.4 233.8,488.4 252.5,484.4 254.1,491.9"/><polygon points="255.3,506.6 244.9,506.9 232.8,505.2 232.9,502.6 253.5,498.1"/><polygon points="275.7,506.0 275.3,506.0 261.2,506.4 259.2,497.0 273.1,493.9"/><polygon points="292.5,503.1 281.4,505.1 278.7,492.6 289.7,490.2"/><polygon points="455.6,313.2 449.3,320.4 440.9,313.0 447.1,305.8"/><polygon points="437.2,310.0 429.8,303.5 436.3,296.1 443.7,302.6"/><polygon points="453.3,277.5 458.6,282.1 445.4,297.2 440.2,292.6"/><polygon points="462.9,285.3 472.6,293.7 467.1,299.9 457.5,291.5"/><polygon points="464.0,303.6 458.8,309.5 449.1,301.0 454.3,295.1"/><polygon points="447.2,271.0 450.2,273.6 439.9,285.4 436.9,282.8"/><polygon points="435.5,265.1 439.6,265.1 443.5,268.4 433.9,279.4 427.7,274.1"/><polygon points="417.0,265.7 417.3,264.6 430.4,264.5 424.0,271.8"/><polygon points="412.7,285.3 416.2,269.9 421.9,274.9"/><polygon points="424.8,277.5 427.6,280.0 416.7,292.4 413.9,290.0"/><polygon points="430.7,283.4 436.4,288.5 426.3,300.1 420.6,295.0"/><polygon points="410.7,329.1 405.4,323.3 408.1,311.3 423.3,314.7"/><polygon points="409.1,305.4 411.6,294.3 428.6,309.2 428.1,309.7"/><polygon points="427.1,344.8 424.8,344.5 414.9,333.7 421.4,326.4 433.7,337.2"/><polygon points="424.4,321.8 432.7,312.2 438.5,317.3 430.2,326.8"/><polygon points="442.0,320.1 446.5,324.0 437.9,333.9 433.4,329.9"/><polygon points="468.2,341.5 469.8,328.5 475.3,333.3"/><polygon points="488.8,346.9 486.4,352.3 468.5,350.0 468.8,348.0 478.0,337.5"/><polygon points="462.8,349.7 448.2,347.9 449.8,335.2 463.1,346.9"/><polygon points="443.2,347.6 433.0,346.3 445.1,332.4"/><polygon points="450.0,327.9 457.4,319.4 465.3,326.4 463.6,339.7"/><polygon points="500.2,318.6 496.3,331.1 491.6,341.3 484.3,334.9 499.3,317.7"/><polygon points="479.8,331.1 475.6,327.4 490.7,310.2 494.9,313.8"/><polygon points="471.2,324.0 460.8,314.9 466.4,308.4 476.9,317.6"/><polygon points="470.0,304.5 476.3,297.3 486.6,306.3 480.3,313.5"/><polygon points="409.6,365.4 401.3,381.8 384.9,373.5 393.2,357.2"/><polygon points="418.5,348.1 413.4,358.2 396.6,349.8 404.9,333.3"/><polygon points="366.7,340.8 370.3,333.9 397.8,329.2 386.9,351.0"/><polygon points="377.5,369.9 356.8,359.5 362.8,348.1 383.2,358.4"/><polygon points="368.5,394.3 349.1,374.4 353.1,366.9 376.4,378.6"/><polygon points="398.1,389.1 392.9,399.5 374.9,399.3 383.7,381.8"/><polygon points="315.5,158.3 292.1,165.9 305.5,149.9"/><polygon points="315.6,136.8 322.0,156.4 308.6,145.2"/><polygon points="324.4,189.0 319.7,194.6 310.9,187.3 315.6,181.7"/><polygon points="326.3,169.1 330.4,181.5 327.1,185.4 318.6,178.3"/><polygon points="312.1,177.7 307.5,183.3 293.6,171.8 308.6,166.9"/><polygon points="323.9,161.0 324.9,163.9 316.3,174.1 313.2,164.5"/><polygon points="290.0,136.0 292.2,135.1 312.4,129.0 313.0,131.1 301.2,145.3"/><polygon points="290.4,144.5 297.2,150.2 285.2,164.6 278.4,159.0"/><polygon points="264.4,147.5 283.7,138.7 285.9,140.6 273.7,155.2"/><polygon points="261.9,153.1 275.7,164.6 270.4,170.8 256.6,159.4"/><polygon points="267.7,175.2 258.8,186.0 254.3,182.3 263.3,171.5"/><polygon points="250.9,179.0 244.3,173.5 252.8,163.3 259.4,168.8"/><polygon points="234.4,165.0 255.2,152.3 240.4,170.0"/><polygon points="250.7,185.2 255.8,189.4 248.0,198.8 241.1,193.0"/><polygon points="244.8,202.3 237.4,211.2 230.1,202.2 237.4,196.2"/><polygon points="222.5,172.8 229.8,167.0 233.6,170.2 226.9,178.2"/><polygon points="236.4,173.7 246.5,182.1 237.4,189.6 230.4,181.0"/><polygon points="213.7,181.3 219.5,176.7 232.7,192.9 227.1,197.6"/><polygon points="254.5,220.7 241.3,216.9 239.7,215.0 243.0,211.1"/><polygon points="246.9,208.2 254.7,198.9 264.6,207.2 256.8,216.5"/><polygon points="268.8,224.1 259.7,221.5 269.0,210.3 275.7,215.8"/><polygon points="290.9,228.2 285.8,229.2 273.7,225.7 279.5,218.7"/><polygon points="303.5,213.5 294.2,224.6 288.0,219.5 297.3,208.3"/><polygon points="284.1,216.9 280.9,214.3 290.9,202.3 294.1,204.9"/><polygon points="303.8,187.5 309.5,192.2 299.8,203.8 294.2,199.1"/><polygon points="313.3,194.9 316.9,197.9 306.8,210.1 303.2,207.1"/><polygon points="280.1,168.1 289.9,176.2 282.8,184.8 273.0,176.7"/><polygon points="294.1,179.1 300.2,184.3 292.5,193.5 286.4,188.4"/><polygon points="289.1,197.1 277.7,210.8 272.6,206.6 284.0,192.9"/><polygon points="268.5,203.1 258.1,194.4 264.3,187.0 274.7,195.7"/><polygon points="266.7,182.9 269.3,179.8 280.9,189.4 278.2,192.5"/><polygon points="252.9,366.4 273.2,358.5 275.9,365.4 255.5,373.3"/><polygon points="277.8,372.0 282.2,383.2 263.0,390.8 258.6,379.5"/><polygon points="250.1,396.2 241.4,375.9 244.0,370.2 246.9,369.0 256.5,393.7"/><polygon points="290.4,403.7 262.4,398.9 285.0,390.0"/><polygon points="313.0,383.8 297.7,403.8 292.9,391.6"/><polygon points="296.2,350.4 303.4,358.8 286.6,373.2 280.1,356.7"/><polygon points="308.6,364.5 317.3,374.6 290.9,384.9 289.4,381.0"/><polygon points="178.2,353.3 175.5,339.5 183.7,337.9 186.4,351.8"/><polygon points="188.7,336.9 196.3,335.4 199.0,349.5 191.4,350.9"/><polygon points="203.4,373.6 195.5,373.7 195.3,355.4 199.8,354.6"/><polygon points="189.9,373.5 182.3,373.6 179.5,358.6 189.8,356.7"/><polygon points="207.8,366.2 205.6,354.7 219.7,352.0 221.9,363.6"/><polygon points="224.8,374.0 208.4,374.1 207.8,371.5 223.8,368.4"/><polygon points="239.0,365.4 239.4,367.3 236.4,373.9 229.0,374.0 227.8,367.5"/><polygon points="235.4,348.6 237.6,360.9 227.5,362.8 225.2,350.6"/><polygon points="215.0,346.8 204.4,348.9 201.7,334.9 212.3,332.8"/><polygon points="232.4,327.9 233.1,331.5 217.6,334.3 216.9,330.9"/><polygon points="233.3,336.0 234.6,343.5 220.5,346.2 219.0,338.6"/><polygon points="217.2,326.4 204.3,328.8 202.8,320.6 215.6,318.1"/><polygon points="229.9,315.1 231.4,323.9 221.9,325.8 220.2,317.0"/><polygon points="212.1,298.4 212.6,298.3 228.4,309.5 228.6,310.4 215.0,313.1"/><polygon points="199.0,300.1 206.9,299.1 209.7,314.0 202.0,315.5"/><polygon points="181.0,333.7 174.2,335.0 172.0,323.7 178.8,322.4"/><polygon points="197.2,319.5 199.1,329.5 185.7,332.1 183.8,322.1"/><polygon points="169.2,304.2 180.6,302.7 182.4,316.5 171.9,318.5"/><polygon points="186.0,301.6 193.8,300.6 196.4,314.2 187.9,315.9"/><polygon points="395.7,122.2 412.6,125.3 409.1,133.9 393.5,127.7"/><polygon points="407.3,138.9 405.1,144.5 389.1,138.1 391.3,132.5"/><polygon points="402.9,149.3 400.0,156.6 384.4,150.3 387.3,143.1"/><polygon points="374.2,120.7 385.9,120.8 389.7,121.5 384.7,134.1 371.0,128.6"/><polygon points="382.7,139.6 379.3,148.2 365.2,142.5 368.6,133.9"/><polygon points="351.2,120.3 355.3,119.7 369.0,119.8 366.1,126.9 351.0,120.9"/><polygon points="363.6,131.3 360.0,140.4 346.1,134.8 349.8,125.8"/><polygon points="322.8,125.5 323.8,125.2 345.2,121.9 340.9,132.7"/><polygon points="325.1,148.7 323.5,143.6 339.7,138.3 341.0,138.9 335.4,152.9"/><polygon points="330.4,167.9 325.9,154.1 334.5,157.6"/><polygon points="321.3,139.1 318.0,128.9 333.4,135.1"/><polygon points="358.6,146.0 368.3,149.9 363.1,162.8 353.4,158.9"/><polygon points="340.7,154.1 346.1,140.7 353.6,143.7 348.2,157.1"/><polygon points="336.1,180.4 335.1,180.0 332.9,173.5 338.8,158.9 343.9,161.0"/><polygon points="354.1,187.3 340.4,182.4 342.4,177.3 355.9,182.7"/><polygon points="344.9,173.0 348.8,163.2 361.1,168.1 357.2,177.9"/><polygon points="388.4,186.3 383.9,197.6 375.8,194.7 380.0,182.9"/><polygon points="370.9,192.5 359.1,188.3 363.7,176.8 374.9,181.3"/><polygon points="374.5,175.4 365.4,171.7 369.5,161.2 378.7,164.9"/><polygon points="370.8,156.9 373.2,150.9 383.2,154.9 380.8,160.9"/><polygon points="393.6,173.3 390.2,181.7 379.1,177.2 382.5,168.8"/><polygon points="398.5,161.4 395.5,168.9 384.2,164.3 387.1,156.9"/><polygon points="265.6,354.8 243.8,363.4 242.3,354.9 264.9,350.9"/><polygon points="241.0,349.3 239.0,338.2 250.6,336.1 252.6,347.2"/><polygon points="255.4,334.8 262.6,333.5 264.7,345.5 257.5,346.8"/><polygon points="283.7,348.9 270.5,354.1 269.5,348.6 283.2,346.1"/><polygon points="269.5,343.9 267.6,333.1 279.7,331.0 281.6,341.7"/><polygon points="295.8,328.2 294.7,343.7 288.1,346.3 285.2,330.1"/><polygon points="285.1,310.5 297.0,318.5 296.7,322.8 287.6,324.4"/><polygon points="280.7,316.3 282.2,324.7 264.3,327.9 262.8,319.4"/><polygon points="260.1,305.5 273.2,303.1 279.2,307.1 279.8,310.5 261.6,313.7"/><polygon points="233.5,309.8 241.3,308.4 243.5,320.5 235.7,321.8"/><polygon points="246.0,307.7 254.8,306.0 257.0,317.9 248.2,319.5"/><polygon points="257.5,323.2 258.6,329.1 238.1,332.8 237.1,326.8"/><polygon points="150.0,336.7 142.2,338.2 139.0,321.5 146.8,320.0"/><polygon points="165.8,316.3 167.4,325.1 153.8,327.7 152.1,318.9"/><polygon points="169.0,329.6 169.7,333.7 154.9,336.5 154.1,332.4"/><polygon points="132.9,288.6 145.4,294.9 137.2,311.3"/><polygon points="154.6,298.7 163.6,303.2 165.1,311.3 157.3,312.8"/><polygon points="149.7,298.5 152.5,313.6 141.1,315.8"/><polygon points="121.9,316.7 127.4,298.2 128.5,295.7 132.1,314.7"/><polygon points="128.7,320.7 133.3,319.8 137.1,339.5 132.5,340.3"/><polygon points="116.5,343.0 118.6,328.6 120.3,322.7 123.5,322.1 127.1,340.9"/><polygon points="114.0,357.9 115.3,348.7 125.7,346.7 127.4,355.3"/><polygon points="128.5,360.0 129.5,365.5 113.9,368.5 113.8,362.8"/><polygon points="129.8,370.8 131.6,380.2 115.0,383.4 114.7,373.7"/><polygon points="132.7,386.1 134.4,394.9 117.2,398.0 115.4,389.4"/><polygon points="136.6,373.4 135.0,365.3 154.8,361.5 156.4,369.6"/><polygon points="160.7,390.3 151.5,391.9 148.8,377.2 157.9,375.4"/><polygon points="146.4,393.2 139.9,394.4 137.0,379.1 143.5,377.8"/><polygon points="176.5,372.4 177.1,375.7 165.9,388.6 163.3,374.9"/><polygon points="173.6,357.4 175.4,367.1 162.4,369.6 160.5,359.9"/><polygon points="130.9,346.0 140.7,344.1 143.3,357.7 133.5,359.5"/><polygon points="146.4,343.3 158.4,341.0 160.9,354.0 148.9,356.3"/><polygon points="163.6,339.3 170.2,338.1 172.9,352.4 166.4,353.6"/><polygon points="507.1,296.9 506.3,301.9 502.5,313.8 496.7,308.7"/><polygon points="505.4,289.3 493.3,303.2 481.1,292.6 493.3,278.7"/><polygon points="510.3,268.1 510.3,272.3 508.6,282.9 496.9,272.7 505.3,263.1"/><polygon points="475.0,287.4 462.8,276.7 474.6,263.1 486.2,274.5"/><polygon points="456.8,271.7 446.1,262.3 459.8,248.4 469.1,257.6"/><polygon points="489.1,248.1 500.4,259.1 491.5,269.3 479.7,257.7"/><polygon points="464.4,242.9 474.0,233.1 484.4,243.3 474.8,253.1"/><polygon points="509.7,238.9 510.5,243.7 510.4,260.2 499.4,249.4"/><polygon points="504.0,235.0 495.4,243.9 479.5,228.4 488.2,219.5"/><polygon points="501.1,205.1 505.5,216.5 507.6,228.7 492.5,213.9"/><polygon points="278.5,286.3 280.6,274.0 287.4,275.2 285.3,287.4"/><polygon points="291.5,275.6 295.8,276.3 293.6,289.2 289.3,288.5"/><polygon points="299.6,277.2 305.2,278.1 303.0,290.7 297.4,289.7"/><polygon points="293.8,293.2 302.4,294.7 301.2,301.5 289.6,299.5"/><polygon points="300.3,305.6 298.8,314.2 285.8,305.5 287.2,303.3"/><polygon points="282.1,290.2 291.0,291.8 288.9,294.9"/><polygon points="276.0,299.2 277.5,290.9 286.5,296.9 282.2,303.3"/><polygon points="324.9,301.9 316.2,306.6 312.5,299.7"/><polygon points="312.7,308.0 303.3,313.0 305.7,299.0 308.1,299.4"/><polygon points="309.1,278.7 322.0,281.0 321.2,285.7 308.3,283.5"/><polygon points="321.1,289.1 319.6,297.8 315.6,297.1 317.1,288.4"/><polygon points="312.5,296.3 306.1,295.2 307.5,287.1 313.9,288.2"/><polygon points="332.8,293.3 332.0,298.0 329.6,299.3 322.8,298.1 323.9,291.8"/><polygon points="325.8,281.5 334.4,283.0 333.3,289.8 324.6,288.3"/><polygon points="281.0,269.9 282.0,264.4 293.2,266.4 292.3,271.9"/><polygon points="282.3,260.9 282.8,257.6 294.7,259.7 294.2,262.9"/><polygon points="283.9,254.1 285.0,247.6 296.0,249.5 294.9,256.0"/><polygon points="300.2,250.3 315.1,252.9 316.3,254.0 310.3,260.6 300.0,251.3"/><polygon points="304.1,268.7 299.6,273.7 295.5,273.0 296.4,267.4"/><polygon points="307.9,263.8 306.2,265.8 297.4,264.3 298.9,255.7"/><polygon points="306.6,243.8 312.7,249.3 305.8,248.1"/><polygon points="299.6,238.1 304.3,242.3 303.5,247.2 298.1,246.3"/><polygon points="285.2,243.9 285.9,239.9 295.9,241.6 295.2,245.7"/><polygon points="286.4,236.7 287.1,232.9 292.4,231.9 296.9,235.9 296.4,238.4"/><polygon points="328.1,269.8 321.8,276.8 315.2,275.7 316.6,267.8"/><polygon points="319.5,256.5 330.3,266.1 317.1,263.8 318.1,258.0"/><polygon points="333.8,269.2 336.3,271.4 335.0,279.4 326.1,277.9"/><polygon points="311.3,275.3 303.7,274.0 313.4,263.2"/><polygon points="194.6,378.6 203.6,378.5 203.7,388.7 194.7,388.8"/><polygon points="173.0,388.6 181.2,379.1 189.7,379.0 189.8,388.4"/><polygon points="181.7,415.0 170.8,394.1 182.2,394.0 182.4,415.0"/><polygon points="187.4,393.4 203.7,393.3 203.7,398.8 187.4,398.9"/><polygon points="203.2,404.1 203.3,414.7 188.2,414.9 188.1,404.3"/><polygon points="203.6,434.1 193.5,437.8 189.1,429.3 203.5,421.8"/><polygon points="186.1,425.6 183.0,419.7 197.7,419.6"/><polygon points="218.9,428.7 208.9,432.4 208.8,419.6 218.8,419.5"/><polygon points="208.2,415.2 208.2,408.9 219.2,408.8 219.3,415.1"/><polygon points="231.5,423.8 224.0,426.6 223.9,409.4 231.4,409.3"/><polygon points="242.9,408.5 238.2,422.0 236.1,422.8 236.0,408.6"/><polygon points="243.2,395.8 245.1,400.1 243.8,403.8 227.3,403.9 227.2,395.9"/><polygon points="235.9,378.7 240.9,390.3 227.1,390.4 227.0,378.8"/><polygon points="221.7,403.9 209.0,404.0 208.9,392.5 221.6,392.4"/><polygon points="208.5,387.1 208.4,378.7 221.8,378.6 221.9,387.0"/><polygon points="155.9,470.7 146.5,462.1 141.5,455.0 154.8,454.1"/><polygon points="187.3,452.3 187.6,453.9 183.5,463.3 161.9,453.9"/><polygon points="162.0,477.1 161.3,476.4 160.1,459.0 168.3,462.5"/><polygon points="181.2,468.9 174.4,484.7 166.3,480.0 172.8,465.2"/></g><g fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M190.1,249.5 L226.8,257.0 L251.3,265.7" stroke="#1a0a2a" stroke-width="3.4"/><path d="M251.3,265.7 L279.8,265.8 L303.8,277.2" stroke="#1a0a2a" stroke-width="3.4"/><path d="M251.3,265.7 L252.3,304.0 L264.1,330.6" stroke="#1a0a2a" stroke-width="3.4"/><path d="M264.1,330.6 L269.4,356.6 L280.6,378.9" stroke="#1a0a2a" stroke-width="3.4"/><path d="M280.6,378.9 L273.0,404.3 L273.8,432.0" stroke="#1a0a2a" stroke-width="3.4"/><path d="M273.8,432.0 L247.1,440.5 L224.3,452.0" stroke="#1a0a2a" stroke-width="3.4"/><path d="M224.3,452.0 L210.9,468.7 L204.5,484.2" stroke="#1a0a2a" stroke-width="3.4"/><path d="M204.5,484.2 L183.3,471.6 L167.0,464.3" stroke="#1a0a2a" stroke-width="3.4"/><path d="M167.0,464.3 L163.1,450.8 L152.8,424.0" stroke="#1a0a2a" stroke-width="3.4"/><path d="M224.3,452.0 L215.9,432.4 L208.3,403.6" stroke="#1a0a2a" stroke-width="3.4"/><path d="M224.3,452.0 L242.7,470.3 L262.3,487.2" stroke="#1a0a2a" stroke-width="3.4"/><path d="M264.1,330.6 L236.1,337.7 L204.1,337.5" stroke="#1a0a2a" stroke-width="3.4"/><path d="M262.3,487.2 L291.7,485.9 L322.2,472.3" stroke="#1a0a2a" stroke-width="3.4"/><path d="M280.6,378.9 L310.6,392.1 L333.3,411.2" stroke="#1a0a2a" stroke-width="3.4"/><path d="M333.3,411.2 L358.8,425.2 L383.2,438.5" stroke="#1a0a2a" stroke-width="3.4"/><path d="M204.1,337.5 L172.8,339.1 L143.0,348.1" stroke="#1a0a2a" stroke-width="3.4"/><path d="M280.6,378.9 L310.3,360.7 L330.2,338.1" stroke="#1a0a2a" stroke-width="3.4"/><path d="M330.2,338.1 L355.8,352.7 L383.9,363.4" stroke="#1a0a2a" stroke-width="3.4"/><path d="M383.9,363.4 L409.3,375.3 L437.9,385.3" stroke="#1a0a2a" stroke-width="3.4"/><path d="M303.8,277.2 L315.6,250.5 L347.8,226.4" stroke="#1a0a2a" stroke-width="3.4"/><path d="M330.2,338.1 L350.6,314.6 L376.8,288.5" stroke="#1a0a2a" stroke-width="3.4"/><path d="M437.9,385.3 L455.8,351.5 L449.0,312.9" stroke="#1a0a2a" stroke-width="3.4"/><path d="M449.0,312.9 L472.1,290.1 L486.8,259.4" stroke="#1a0a2a" stroke-width="3.4"/><path d="M486.8,259.4 L471.4,231.0 L442.9,220.1" stroke="#1a0a2a" stroke-width="3.4"/><path d="M442.9,220.1 L440.6,192.8 L432.5,168.0" stroke="#1a0a2a" stroke-width="3.4"/><path d="M432.5,168.0 L400.8,162.0 L365.0,152.0" stroke="#1a0a2a" stroke-width="3.4"/><path d="M347.8,226.4 L312.9,206.1 L277.3,180.6" stroke="#1a0a2a" stroke-width="3.4"/><path d="M322.2,472.3 L327.7,444.2 L333.3,411.2" stroke="#1a0a2a" stroke-width="3.4"/><path d="M437.9,385.3 L411.5,419.3 L383.2,438.5" stroke="#1a0a2a" stroke-width="3.4"/><path d="M376.8,288.5 L384.8,327.2 L383.9,363.4" stroke="#1a0a2a" stroke-width="3.4"/><path d="M152.8,424.0 L179.3,416.7 L208.3,403.6" stroke="#1a0a2a" stroke-width="3.4"/><path d="M449.0,312.9 L412.7,335.8 L383.9,363.4" stroke="#1a0a2a" stroke-width="3.4"/><path d="M280.6,378.9 L239.6,371.7 L204.1,337.5" stroke="#1a0a2a" stroke-width="3.4"/><path d="M330.2,338.1 L124.8,297.3" stroke="#1a0a2a" stroke-width="4.8"/><path d="M330.2,338.1 L465.1,148.1" stroke="#1a0a2a" stroke-width="4.8"/><path d="M330.2,338.1 L368.5,478.8" stroke="#1a0a2a" stroke-width="4.8"/><path d="M190.1,249.5 L226.8,257.0 L251.3,265.7" stroke="#ff2bd6" stroke-width="1.8"/><path d="M251.3,265.7 L279.8,265.8 L303.8,277.2" stroke="#ff2bd6" stroke-width="1.8"/><path d="M251.3,265.7 L252.3,304.0 L264.1,330.6" stroke="#ff2bd6" stroke-width="1.8"/><path d="M264.1,330.6 L269.4,356.6 L280.6,378.9" stroke="#ff2bd6" stroke-width="1.8"/><path d="M280.6,378.9 L273.0,404.3 L273.8,432.0" stroke="#ff2bd6" stroke-width="1.8"/><path d="M273.8,432.0 L247.1,440.5 L224.3,452.0" stroke="#ff2bd6" stroke-width="1.8"/><path d="M224.3,452.0 L210.9,468.7 L204.5,484.2" stroke="#ff2bd6" stroke-width="1.8"/><path d="M204.5,484.2 L183.3,471.6 L167.0,464.3" stroke="#ff2bd6" stroke-width="1.8"/><path d="M167.0,464.3 L163.1,450.8 L152.8,424.0" stroke="#ff2bd6" stroke-width="1.8"/><path d="M224.3,452.0 L215.9,432.4 L208.3,403.6" stroke="#ff2bd6" stroke-width="1.8"/><path d="M224.3,452.0 L242.7,470.3 L262.3,487.2" stroke="#ff2bd6" stroke-width="1.8"/><path d="M264.1,330.6 L236.1,337.7 L204.1,337.5" stroke="#ff2bd6" stroke-width="1.8"/><path d="M262.3,487.2 L291.7,485.9 L322.2,472.3" stroke="#ff2bd6" stroke-width="1.8"/><path d="M280.6,378.9 L310.6,392.1 L333.3,411.2" stroke="#ff2bd6" stroke-width="1.8"/><path d="M333.3,411.2 L358.8,425.2 L383.2,438.5" stroke="#ff2bd6" stroke-width="1.8"/><path d="M204.1,337.5 L172.8,339.1 L143.0,348.1" stroke="#ff2bd6" stroke-width="1.8"/><path d="M280.6,378.9 L310.3,360.7 L330.2,338.1" stroke="#ff2bd6" stroke-width="1.8"/><path d="M330.2,338.1 L355.8,352.7 L383.9,363.4" stroke="#ff2bd6" stroke-width="1.8"/><path d="M383.9,363.4 L409.3,375.3 L437.9,385.3" stroke="#ff2bd6" stroke-width="1.8"/><path d="M303.8,277.2 L315.6,250.5 L347.8,226.4" stroke="#ff2bd6" stroke-width="1.8"/><path d="M330.2,338.1 L350.6,314.6 L376.8,288.5" stroke="#ff2bd6" stroke-width="1.8"/><path d="M437.9,385.3 L455.8,351.5 L449.0,312.9" stroke="#ff2bd6" stroke-width="1.8"/><path d="M449.0,312.9 L472.1,290.1 L486.8,259.4" stroke="#ff2bd6" stroke-width="1.8"/><path d="M486.8,259.4 L471.4,231.0 L442.9,220.1" stroke="#ff2bd6" stroke-width="1.8"/><path d="M442.9,220.1 L440.6,192.8 L432.5,168.0" stroke="#ff2bd6" stroke-width="1.8"/><path d="M432.5,168.0 L400.8,162.0 L365.0,152.0" stroke="#ff2bd6" stroke-width="1.8"/><path d="M347.8,226.4 L312.9,206.1 L277.3,180.6" stroke="#ff2bd6" stroke-width="1.8"/><path d="M322.2,472.3 L327.7,444.2 L333.3,411.2" stroke="#ff2bd6" stroke-width="1.8"/><path d="M437.9,385.3 L411.5,419.3 L383.2,438.5" stroke="#ff2bd6" stroke-width="1.8"/><path d="M376.8,288.5 L384.8,327.2 L383.9,363.4" stroke="#ff2bd6" stroke-width="1.8"/><path d="M152.8,424.0 L179.3,416.7 L208.3,403.6" stroke="#ff2bd6" stroke-width="1.8"/><path d="M449.0,312.9 L412.7,335.8 L383.9,363.4" stroke="#ff2bd6" stroke-width="1.8"/><path d="M280.6,378.9 L239.6,371.7 L204.1,337.5" stroke="#ff2bd6" stroke-width="1.8"/><path d="M330.2,338.1 L124.8,297.3" stroke="#ff2bd6" stroke-width="3.2"/><path d="M330.2,338.1 L465.1,148.1" stroke="#ff2bd6" stroke-width="3.2"/><path d="M330.2,338.1 L368.5,478.8" stroke="#ff2bd6" stroke-width="3.2"/></g><path d="M111.4,358.6 L115.7,328.0 M115.7,328.0 L122.2,306.0 M128.6,289.0 L138.4,267.1 M138.4,267.1 L156.2,238.1 M156.2,238.1 L177.7,210.9 M177.7,210.9 L202.5,186.2 M202.5,186.2 L230.1,164.5 M230.1,164.5 L259.9,146.3 M259.9,146.3 L291.1,132.1 M291.1,132.1 L323.1,122.4 M323.1,122.4 L355.1,117.5 M355.1,117.5 L386.1,117.7 M386.1,117.7 L415.4,123.0 M415.4,123.0 L441.9,133.2 M441.9,133.2 L457.4,143.2 M471.5,154.6 L484.2,167.3 M484.2,167.3 L498.7,190.0 M498.7,190.0 L508.5,215.6 M508.5,215.6 L513.3,243.4 M513.3,243.4 L513.2,272.6 M513.2,272.6 L508.4,302.4 M508.4,302.4 L499.1,332.2 M499.1,332.2 L485.6,361.3 M485.6,361.3 L468.3,389.2 M468.3,389.2 L447.5,415.5 M447.5,415.5 L423.7,439.5 M423.7,439.5 L397.2,460.7 M397.2,460.7 L376.2,474.0 M360.3,482.7 L338.3,493.2 M338.3,493.2 L307.0,503.4 M307.0,503.4 L275.6,509.0 M275.6,509.0 L244.8,509.8 M244.8,509.8 L215.4,505.6 M215.4,505.6 L188.4,496.4 M188.4,496.4 L164.5,482.4 M164.5,482.4 L144.4,464.0 M144.4,464.0 L128.8,441.7 M128.8,441.7 L117.9,416.2 M117.9,416.2 L112.1,388.2 M112.1,388.2 L111.4,358.6" fill="none" stroke="#00343f" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"/><g fill="#00343f" stroke="#00e5ff" stroke-width="0.6"><circle cx="111.4" cy="358.6" r="2.6"/><circle cx="115.7" cy="328.0" r="2.6"/><circle cx="124.8" cy="297.3" r="2.6"/><circle cx="138.4" cy="267.1" r="2.6"/><circle cx="156.2" cy="238.1" r="2.6"/><circle cx="177.7" cy="210.9" r="2.6"/><circle cx="202.5" cy="186.2" r="2.6"/><circle cx="230.1" cy="164.5" r="2.6"/><circle cx="259.9" cy="146.3" r="2.6"/><circle cx="291.1" cy="132.1" r="2.6"/><circle cx="323.1" cy="122.4" r="2.6"/><circle cx="355.1" cy="117.5" r="2.6"/><circle cx="386.1" cy="117.7" r="2.6"/><circle cx="415.4" cy="123.0" r="2.6"/><circle cx="441.9" cy="133.2" r="2.6"/><circle cx="465.1" cy="148.1" r="2.6"/><circle cx="484.2" cy="167.3" r="2.6"/><circle cx="498.7" cy="190.0" r="2.6"/><circle cx="508.5" cy="215.6" r="2.6"/><circle cx="513.3" cy="243.4" r="2.6"/><circle cx="513.2" cy="272.6" r="2.6"/><circle cx="508.4" cy="302.4" r="2.6"/><circle cx="499.1" cy="332.2" r="2.6"/><circle cx="485.6" cy="361.3" r="2.6"/><circle cx="468.3" cy="389.2" r="2.6"/><circle cx="447.5" cy="415.5" r="2.6"/><circle cx="423.7" cy="439.5" r="2.6"/><circle cx="397.2" cy="460.7" r="2.6"/><circle cx="368.5" cy="478.8" r="2.6"/><circle cx="338.3" cy="493.2" r="2.6"/><circle cx="307.0" cy="503.4" r="2.6"/><circle cx="275.6" cy="509.0" r="2.6"/><circle cx="244.8" cy="509.8" r="2.6"/><circle cx="215.4" cy="505.6" r="2.6"/><circle cx="188.4" cy="496.4" r="2.6"/><circle cx="164.5" cy="482.4" r="2.6"/><circle cx="144.4" cy="464.0" r="2.6"/><circle cx="128.8" cy="441.7" r="2.6"/><circle cx="117.9" cy="416.2" r="2.6"/><circle cx="112.1" cy="388.2" r="2.6"/></g><g fill="#00343f" stroke="#00e5ff" stroke-width="0.8"><rect x="121.8" y="294.3" width="6.0" height="6.0"/><rect x="462.1" y="145.1" width="6.0" height="6.0"/><rect x="365.5" y="475.8" width="6.0" height="6.0"/></g><g font-family="Georgia, serif" font-size="8" text-anchor="middle"><text x="190.1" y="252.5" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="322.2" y="475.3" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="442.9" y="223.1" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="251.3" y="268.7" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Noble</text><text x="437.9" y="388.3" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="383.2" y="441.5" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Garrison</text><text x="347.8" y="229.4" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Slums</text><text x="224.3" y="455.0" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="330.2" y="341.1" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Market</text><text x="204.5" y="487.2" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="333.3" y="414.2" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Garrison</text><text x="376.8" y="291.5" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="152.8" y="427.0" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="273.8" y="435.0" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Noble</text><text x="432.5" y="171.0" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Garrison</text><text x="262.3" y="490.2" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Temple</text><text x="449.0" y="315.9" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="383.9" y="366.4" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Noble</text><text x="277.3" y="183.6" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="280.6" y="381.9" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Garrison</text><text x="204.1" y="340.5" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Temple</text><text x="365.0" y="155.0" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Residential</text><text x="264.1" y="333.6" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="143.0" y="351.1" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="486.8" y="262.4" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Garrison</text><text x="303.8" y="280.2" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Slums</text><text x="208.3" y="406.6" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text><text x="167.0" y="467.3" stroke="#05060a" stroke-width="2.5" paint-order="stroke" fill="#aef7ff">Craftsmen</text></g><text x="315.0" y="20" text-anchor="middle" font-family="Georgia, serif" font-size="16" font-weight="bold" stroke="#05060a" stroke-width="3" paint-order="stroke" fill="#aef7ff">Caldwingate</text></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="624" height="408" viewBox="0 0 624 408"><rect width="624" height="408" fill="#071925"/><g fill="#123a55"><rect x="12.0" y="12.0" width="60.0" height="12.0"/><rect x="120.0" y="12.0" width="36.0" height="12.0"/><rect x="540.0" y="12.0" width="72.0" height="12.0"/><rect x="12.0" y="24.0" width="60.0" height="12.0"/><rect x="120.0" y="24.0" width="36.0" height="12.0"/><rect x="540.0" y="24.0" width="72.0" height="12.0"/><rect x="12.0" y="36.0" width="60.0" height="12.0"/><rect x="120.0" y="36.0" width="36.0" height="12.0"/><rect x="360.0" y="36.0" width="144.0" height="12.0"/><rect x="540.0" y="36.0" width="72.0" height="12.0"/><rect x="12.0" y="48.0" width="60.0" height="12.0"/><rect x="120.0" y="48.0" width="36.0" height="12.0"/><rect x="360.0" y="48.0" width="144.0" height="12.0"/><rect x="540.0" y="48.0" width="72.0" height="12.0"/><rect x="12.0" y="60.0" width="60.0" height="12.0"/><rect x="120.0" y="60.0" width="36.0" height="12.0"/><rect x="360.0" y="60.0" width="144.0" height="12.0"/><rect x="540.0" y="60.0" width="72.0" height="12.0"/><rect x="12.0" y="72.0" width="600.0" height="12.0"/><rect x="12.0" y="84.0" width="60.0" height="12.0"/><rect x="120.0" y="84.0" width="36.0" height="12.0"/><rect x="252.0" y="84.0" width="360.0" height="12.0"/><rect x="12.0" y="96.0" width="60.0" height="12.0"/><rect x="120.0" y="96.0" width="36.0" height="12.0"/><rect x="252.0" y="96.0" width="72.0" height="12.0"/><rect x="360.0" y="96.0" width="144.0" height="12.0"/><rect x="540.0" y="96.0" width="72.0" height="12.0"/><rect x="12.0" y="108.0" width="60.0" height="12.0"/><rect x="120.0" y="108.0" width="36.0" height="12.0"/><rect x="252.0" y="108.0" width="72.0" height="12.0"/><rect x="360.0" y="108.0" width="144.0" height="12.0"/><rect x="540.0" y="108.0" width="72.0" height="12.0"/><rect x="12.0" y="120.0" width="60.0" height="12.0"/><rect x="120.0" y="120.0" width="36.0" height="12.0"/><rect x="252.0" y="120.0" width="72.0" height="12.0"/><rect x="396.0" y="120.0" width="12.0" height="12.0"/><rect x="540.0" y="120.0" width="72.0" height="12.0"/><rect x="12.0" y="132.0" width="60.0" height="12.0"/><rect x="132.0" y="132.0" width="12.0" height="12.0"/><rect x="252.0" y="132.0" width="72.0" height="12.0"/><rect x="396.0" y="132.0" width="12.0" height="12.0"/><rect x="540.0" y="132.0" width="72.0" height="12.0"/><rect x="36.0" y="144.0" width="24.0" height="12.0"/><rect x="132.0" y="144.0" width="12.0" height="12.0"/><rect x="252.0" y="144.0" width="72.0" height="12.0"/><rect x="396.0" y="144.0" width="12.0" height="12.0"/><rect x="540.0" y="144.0" width="72.0" height="12.0"/><rect x="36.0" y="156.0" width="24.0" height="12.0"/><rect x="132.0" y="156.0" width="12.0" height="12.0"/><rect x="300.0" y="156.0" width="12.0" height="12.0"/><rect x="396.0" y="156.0" width="12.0" height="12.0"/><rect x="576.0" y="156.0" width="12.0" height="12.0"/><rect x="12.0" y="168.0" width="72.0" height="12.0"/><rect x="132.0" y="168.0" width="12.0" height="12.0"/><rect x="300.0" y="168.0" width="12.0" height="12.0"/><rect x="396.0" y="168.0" width="12.0" height="12.0"/><rect x="576.0" y="168.0" width="12.0" height="12.0"/><rect x="12.0" y="180.0" width="72.0" height="12.0"/><rect x="120.0" y="180.0" width="84.0" height="12.0"/><rect x="300.0" y="180.0" width="12.0" height="12.0"/><rect x="396.0" y="180.0" width="12.0" height="12.0"/><rect x="576.0" y="180.0" width="12.0" height="12.0"/><rect x="12.0" y="192.0" width="72.0" height="12.0"/><rect x="120.0" y="192.0" width="84.0" height="12.0"/><rect x="300.0" y="192.0" width="12.0" height="12.0"/><rect x="396.0" y="192.0" width="12.0" height="12.0"/><rect x="576.0" y="192.0" width="12.0" height="12.0"/><rect x="12.0" y="204.0" width="192.0" height="12.0"/><rect x="276.0" y="204.0" width="48.0" height="12.0"/><rect x="396.0" y="204.0" width="12.0" height="12.0"/><rect x="468.0" y="204.0" width="120.0" height="12.0"/><rect x="12.0" y="216.0" width="72.0" height="12.0"/><rect x="120.0" y="216.0" width="84.0" height="12.0"/><rect x="276.0" y="216.0" width="48.0" height="12.0"/><rect x="360.0" y="216.0" width="84.0" height="12.0"/><rect x="468.0" y="216.0" width="120.0" height="12.0"/><rect x="12.0" y="228.0" width="72.0" height="12.0"/><rect x="132.0" y="228.0" width="12.0" height="12.0"/><rect x="276.0" y="228.0" width="312.0" height="12.0"/><rect x="36.0" y="240.0" width="12.0" height="12.0"/><rect x="60.0" y="240.0" width="12.0" height="12.0"/><rect x="132.0" y="240.0" width="12.0" height="12.0"/><rect x="276.0" y="240.0" width="48.0" height="12.0"/><rect x="360.0" y="240.0" width="84.0" height="12.0"/><rect x="468.0" y="240.0" width="108.0" height="12.0"/><rect x="36.0" y="252.0" width="12.0" height="12.0"/><rect x="60.0" y="252.0" width="12.0" height="12.0"/><rect x="132.0" y="252.0" width="12.0" height="12.0"/><rect x="396.0" y="252.0" width="12.0" height="12.0"/><rect x="552.0" y="252.0" width="12.0" height="12.0"/><rect x="36.0" y="264.0" width="48.0" height="12.0"/><rect x="132.0" y="264.0" width="12.0" height="12.0"/><rect x="156.0" y="264.0" width="36.0" height="12.0"/><rect x="396.0" y="264.0" width="12.0" height="12.0"/><rect x="552.0" y="264.0" width="12.0" height="12.0"/><rect x="36.0" y="276.0" width="48.0" height="12.0"/><rect x="132.0" y="276.0" width="12.0" height="12.0"/><rect x="156.0" y="276.0" width="36.0" height="12.0"/><rect x="396.0" y="276.0" width="12.0" height="12.0"/><rect x="552.0" y="276.0" width="12.0" height="12.0"/><rect x="36.0" y="288.0" width="48.0" height="12.0"/><rect x="132.0" y="288.0" width="12.0" height="12.0"/><rect x="156.0" y="288.0" width="36.0" height="12.0"/><rect x="348.0" y="288.0" width="96.0" height="12.0"/><rect x="552.0" y="288.0" width="12.0" height="12.0"/><rect x="36.0" y="300.0" width="48.0" height="12.0"/><rect x="132.0" y="300.0" width="12.0" height="12.0"/><rect x="156.0" y="300.0" width="36.0" height="12.0"/><rect x="348.0" y="300.0" width="96.0" height="12.0"/><rect x="552.0" y="300.0" width="12.0" height="12.0"/><rect x="36.0" y="312.0" width="48.0" height="12.0"/><rect x="132.0" y="312.0" width="12.0" height="12.0"/><rect x="156.0" y="312.0" width="36.0" height="12.0"/><rect x="348.0" y="312.0" width="96.0" height="12.0"/><rect x="552.0" y="312.0" width="12.0" height="12.0"/><rect x="36.0" y="324.0" width="408.0" height="12.0"/><rect x="504.0" y="324.0" width="96.0" height="12.0"/><rect x="48.0" y="336.0" width="36.0" height="12.0"/><rect x="132.0" y="336.0" width="12.0" height="12.0"/><rect x="156.0" y="336.0" width="36.0" height="12.0"/><rect x="264.0" y="336.0" width="36.0" height="12.0"/><rect x="348.0" y="336.0" width="96.0" height="12.0"/><rect x="504.0" y="336.0" width="96.0" height="12.0"/><rect x="48.0" y="348.0" width="36.0" height="12.0"/><rect x="132.0" y="348.0" width="12.0" height="12.0"/><rect x="156.0" y="348.0" width="36.0" height="12.0"/><rect x="264.0" y="348.0" width="36.0" height="12.0"/><rect x="348.0" y="348.0" width="96.0" height="12.0"/><rect x="504.0" y="348.0" width="96.0" height="12.0"/><rect x="48.0" y="360.0" width="36.0" height="12.0"/><rect x="132.0" y="360.0" width="468.0" height="12.0"/><rect x="48.0" y="372.0" width="36.0" height="12.0"/><rect x="156.0" y="372.0" width="36.0" height="12.0"/><rect x="264.0" y="372.0" width="36.0" height="12.0"/><rect x="504.0" y="372.0" width="96.0" height="12.0"/><rect x="48.0" y="384.0" width="36.0" height="12.0"/><rect x="504.0" y="384.0" width="96.0" height="12.0"/></g><g fill="#1a5e78" stroke="#8fe6ff" stroke-width="2" stroke-linejoin="round"><rect x="12.0" y="12.0" width="60.0" height="132.0"/><rect x="120.0" y="12.0" width="36.0" height="120.0"/><rect x="12.0" y="168.0" width="72.0" height="72.0"/><rect x="120.0" y="180.0" width="84.0" height="48.0"/><rect x="48.0" y="264.0" width="36.0" height="132.0"/><rect x="156.0" y="264.0" width="36.0" height="120.0"/><rect x="252.0" y="72.0" width="72.0" height="84.0"/><rect x="360.0" y="36.0" width="144.0" height="84.0"/><rect x="540.0" y="12.0" width="72.0" height="144.0"/><rect x="276.0" y="204.0" width="48.0" height="48.0"/><rect x="360.0" y="216.0" width="84.0" height="36.0"/><rect x="264.0" y="336.0" width="36.0" height="48.0"/><rect x="348.0" y="288.0" width="96.0" height="72.0"/><rect x="468.0" y="204.0" width="108.0" height="48.0"/><rect x="504.0" y="324.0" width="96.0" height="72.0"/></g><g font-family="Georgia, serif" font-size="11" text-anchor="middle"><text x="42.0" y="82.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">1</text><text x="138.0" y="76.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">2</text><text x="48.0" y="208.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">3</text><text x="162.0" y="208.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">4</text><text x="66.0" y="334.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">5</text><text x="174.0" y="328.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">6</text><text x="288.0" y="118.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">7</text><text x="432.0" y="82.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">8</text><text x="576.0" y="88.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">9</text><text x="300.0" y="232.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">10</text><text x="402.0" y="238.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">11</text><text x="282.0" y="364.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">12</text><text x="396.0" y="328.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">13</text><text x="522.0" y="232.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">14</text><text x="552.0" y="364.0" stroke="#071925" stroke-width="3" paint-order="stroke" fill="#d8f4ff">15</text></g></svg>
@@ -78,15 +78,16 @@ def render_template(template: str, sea_level: float, seed: int) -> str:
78
78
  return RegionalSVGRenderer(scale=MAP_SCALE).render(t)
79
79
 
80
80
 
81
- def render_dungeon(seed: int = 3) -> str:
81
+ def render_dungeon(seed: int = 3, theme: str = "parchment") -> str:
82
82
  dungeon = DungeonGenerator(SeededRNG(seed)).generate(DUNGEON_W, DUNGEON_H)
83
- return DungeonSVGRenderer(scale=DUNGEON_SCALE).render(dungeon, labels=True)
83
+ return DungeonSVGRenderer(scale=DUNGEON_SCALE, theme=theme).render(
84
+ dungeon, labels=True)
84
85
 
85
86
 
86
- def render_settlement(preset: str | None, seed: int) -> str:
87
+ def render_settlement(preset: str | None, seed: int, theme: str = "parchment") -> str:
87
88
  cfg = SettlementConfig.preset(preset) if preset else None
88
89
  town = SettlementGenerator(SeededRNG(seed)).generate(TOWN_W, TOWN_H, cfg)
89
- return SettlementSVGRenderer(scale=TOWN_SCALE).render(town)
90
+ return SettlementSVGRenderer(scale=TOWN_SCALE, theme=theme).render(town)
90
91
 
91
92
 
92
93
  def render_roads(seed: int = 7) -> str:
@@ -198,6 +199,9 @@ def main() -> None:
198
199
  emit("theme-neon", render_themed("neon"))
199
200
  emit("theme-dune", render_themed("dune"))
200
201
  emit("theme-blueprint", render_themed("blueprint"))
202
+ # The same theme drives the town & dungeon renderers too.
203
+ emit("theme-citadel-neon", render_settlement("citadel", seed=3, theme="neon"))
204
+ emit("theme-dungeon-blueprint", render_dungeon(theme="blueprint"))
201
205
 
202
206
  # AtlasRenderer thumbnail — a direct PNG (no SVG), from the bundled sample pack.
203
207
  atlas_png = render_atlas(seed=5)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "mapwright"
7
- version = "0.17.0"
7
+ version = "0.18.0"
8
8
  description = "Domain-neutral procedural fantasy map & world generation: Voronoi terrain, hydraulic erosion, biomes, rivers, Markov place-names, and shaded-relief SVG."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -24,6 +24,11 @@ Quickstart::
24
24
  svg = RegionalSVGRenderer().render(terrain)
25
25
  """
26
26
 
27
+ from .affordances import (
28
+ CellSummary,
29
+ environment_affordances,
30
+ summarize_cells,
31
+ )
27
32
  from .atlas_renderer import ArtPack, AtlasRenderer
28
33
  from .config import WorldMapConfig, PRESETS
29
34
  from .dungeon import Dungeon, DungeonConfig, DungeonGenerator, Rect
@@ -55,12 +60,15 @@ from .terrain import (
55
60
  compute_cell_polygons,
56
61
  )
57
62
 
58
- __version__ = "0.17.0"
63
+ __version__ = "0.18.0"
59
64
 
60
65
  __all__ = [
61
66
  "SeededRNG",
62
67
  "WorldMapConfig",
63
68
  "PRESETS",
69
+ "CellSummary",
70
+ "environment_affordances",
71
+ "summarize_cells",
64
72
  "NameGenerator",
65
73
  "MarkovNameGenerator",
66
74
  "NAMEBASES",
@@ -0,0 +1,168 @@
1
+ """Neutral environmental affordances and cell aggregation.
2
+
3
+ Two domain-neutral helpers that sit on top of the terrain model:
4
+
5
+ * :func:`environment_affordances` — *what a place affords*: the coarse,
6
+ ecology-level hazards/opportunities implied by a biome plus its climate
7
+ (a swamp breeds disease; a desert is short on water; a hot, wet forest gets
8
+ both predators and biting-insect disease vectors). Tags are neutral strings —
9
+ a host app decides what, if anything, they *mechanically* mean (a D&D layer
10
+ might map ``"scarce_water"`` to an exhaustion clock, ``"predator"`` to an
11
+ encounter table). This library never reaches into game rules.
12
+
13
+ * :func:`summarize_cells` — reduce a set of :class:`TerrainCell` (a place's
14
+ footprint, an explored area, a whole map) to one :class:`CellSummary`:
15
+ dominant biome, mean climate, hydrology flags, and the affordance tags for
16
+ the aggregate. Pure stats; the caller chooses which cells make up the place.
17
+
18
+ Both are deterministic — same cells in, same summary out — so they fit the
19
+ seed-reproducible contract of the rest of mapwright.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from collections import Counter
25
+ from dataclasses import dataclass
26
+ from typing import Iterable
27
+
28
+ from .terrain import Biome, TerrainCell
29
+
30
+ # Base affordances intrinsic to each biome, independent of climate swing.
31
+ # Neutral, ecology-level vocabulary — not game mechanics.
32
+ _BIOME_BASE_AFFORDANCES: dict[Biome, tuple[str, ...]] = {
33
+ Biome.OCEAN: ("drowning", "no_shelter", "open_water"),
34
+ Biome.COAST: ("tides", "open_water"),
35
+ Biome.BEACH: ("tides", "exposure"),
36
+ Biome.DESERT: ("scarce_water", "exposure", "extreme_heat"),
37
+ Biome.PLAINS: ("exposure",),
38
+ Biome.FOREST: ("dense_cover", "predator", "easy_to_get_lost"),
39
+ Biome.SWAMP: ("disease_vector", "difficult_terrain", "predator", "miasma"),
40
+ Biome.HILLS: ("difficult_terrain",),
41
+ Biome.MOUNTAIN: ("rockfall", "thin_air", "difficult_terrain", "extreme_cold"),
42
+ Biome.TUNDRA: ("extreme_cold", "exposure", "scarce_food"),
43
+ Biome.SNOW: ("extreme_cold", "exposure", "whiteout"),
44
+ Biome.RIVER: ("currents", "water_source"),
45
+ Biome.LAKE: ("cold_water", "water_source"),
46
+ }
47
+
48
+ # Climate thresholds (on the 0..1 cell scales) that add extra affordances on top
49
+ # of the biome base — this is what makes a *hot, wet* forest read as a steamy
50
+ # jungle (predators + disease vectors) rather than a temperate wood.
51
+ _ARID = 0.22
52
+ _HUMID = 0.78
53
+ _COLD = 0.22
54
+ _HOT = 0.78
55
+
56
+
57
+ def environment_affordances(
58
+ biome: Biome, temperature: float, moisture: float
59
+ ) -> tuple[str, ...]:
60
+ """Neutral affordance tags for a biome under a given climate.
61
+
62
+ ``temperature`` and ``moisture`` are the 0..1 cell scales. Returns a
63
+ de-duplicated tuple, biome-base tags first then climate-driven additions,
64
+ each in a stable order so the result is reproducible.
65
+ """
66
+ base = _BIOME_BASE_AFFORDANCES.get(biome, ())
67
+ tags: list[str] = list(base)
68
+ # A freshwater biome (river/lake) affords drinking water, so heat/aridity must
69
+ # not also call water "scarce" — that's contradictory. An ocean keeps
70
+ # scarce_water on purpose: plenty of water at sea, none of it drinkable.
71
+ has_fresh_water = "water_source" in base
72
+
73
+ extra: list[str] = []
74
+ if moisture <= _ARID and not has_fresh_water:
75
+ extra.append("scarce_water")
76
+ if moisture >= _HUMID:
77
+ # standing water + warmth is what actually breeds the biting insects /
78
+ # waterborne illness; pair with heat below for the full jungle effect.
79
+ extra.append("disease_vector")
80
+ if temperature >= _HOT:
81
+ extra.append("extreme_heat")
82
+ if not has_fresh_water:
83
+ extra.append("scarce_water")
84
+ if temperature <= _COLD:
85
+ extra.append("extreme_cold")
86
+
87
+ # de-dup, preserving first-seen order (base before climate extras)
88
+ seen: dict[str, None] = {}
89
+ for tag in (*tags, *extra):
90
+ seen.setdefault(tag, None)
91
+ return tuple(seen)
92
+
93
+
94
+ @dataclass
95
+ class CellSummary:
96
+ """Aggregate of a set of terrain cells — one place's environment."""
97
+
98
+ dominant_biome: Biome
99
+ temperature: float # mean over the cells, 0..1
100
+ moisture: float # mean over the cells, 0..1
101
+ mean_height: float # mean raw height, 0..1 (compare to a TerrainResult.sea_level)
102
+ has_river: bool
103
+ has_lake: bool
104
+ water_fraction: float # fraction of cells that are ocean or lake, 0..1
105
+ cell_count: int
106
+ affordances: tuple[str, ...]
107
+
108
+ def to_dict(self) -> dict:
109
+ return {
110
+ "dominant_biome": int(self.dominant_biome),
111
+ "temperature": self.temperature,
112
+ "moisture": self.moisture,
113
+ "mean_height": self.mean_height,
114
+ "has_river": self.has_river,
115
+ "has_lake": self.has_lake,
116
+ "water_fraction": self.water_fraction,
117
+ "cell_count": self.cell_count,
118
+ "affordances": list(self.affordances),
119
+ }
120
+
121
+ @classmethod
122
+ def from_dict(cls, data: dict) -> "CellSummary":
123
+ return cls(
124
+ dominant_biome=Biome(int(data["dominant_biome"])),
125
+ temperature=float(data["temperature"]),
126
+ moisture=float(data["moisture"]),
127
+ mean_height=float(data["mean_height"]),
128
+ has_river=bool(data["has_river"]),
129
+ has_lake=bool(data["has_lake"]),
130
+ water_fraction=float(data["water_fraction"]),
131
+ cell_count=int(data["cell_count"]),
132
+ affordances=tuple(data["affordances"]),
133
+ )
134
+
135
+
136
+ def summarize_cells(cells: Iterable[TerrainCell]) -> CellSummary:
137
+ """Reduce a set of cells to one :class:`CellSummary`.
138
+
139
+ The dominant biome is the modal biome over the cells (ties broken by the
140
+ lowest :class:`Biome` value, for determinism). Climate fields are means.
141
+ Raises ``ValueError`` on an empty input — an empty footprint is a caller
142
+ bug, not something to paper over with a default.
143
+ """
144
+ cells = list(cells)
145
+ if not cells:
146
+ raise ValueError("summarize_cells requires at least one cell")
147
+
148
+ counts = Counter(c.biome for c in cells)
149
+ top = max(counts.values())
150
+ dominant = min(b for b, n in counts.items() if n == top)
151
+
152
+ n = len(cells)
153
+ temperature = sum(c.temperature for c in cells) / n
154
+ moisture = sum(c.moisture for c in cells) / n
155
+ mean_height = sum(c.height for c in cells) / n
156
+ water_fraction = sum(c.is_water or c.is_lake for c in cells) / n
157
+
158
+ return CellSummary(
159
+ dominant_biome=dominant,
160
+ temperature=temperature,
161
+ moisture=moisture,
162
+ mean_height=mean_height,
163
+ has_river=any(c.is_river for c in cells),
164
+ has_lake=any(c.is_lake for c in cells),
165
+ water_fraction=water_fraction,
166
+ cell_count=n,
167
+ affordances=environment_affordances(dominant, temperature, moisture),
168
+ )