Releases: sligara7/mapwright
Release list
v0.27.1 — review fixes: continuous rivers, robust config, perf
Bug fixes, cleanup, and a byte-identical performance win from a full-codebase
review. One behaviour change: rivers are now continuous (see Fixed).
Fixed
- Rivers are continuous source→mouth polylines again. They were traced
highest-flux-first, which shattered every river into 2-cell fragments — harmless
on the rendered map (the pieces tile), butTerrainResult.riversand its
serialisation were fragmented, with per-fragment widths. Now traced
headwaters-first, andRiver.widthcomes from the downstream (mouth) end.
This changesriversoutput for a given seed. WorldMapConfig.from_dictno longer crashes on noisy payloads. Astr,
None,NaN, orinfvalue now coerces to a finite number (or the field
default) and clamps, instead of raisingTypeError— honouring the documented
"safe for sloppy LLM/JSON input" contract.edge_falloffwas applied squared in multi-continent worlds; now applied
once. (Only affectedcontinents>1withedge_falloff≠1.0; no preset uses that.)- Smart labels no longer misalign colour/style when a region or feature has an
empty name, and features never get an empty name.
Changed
- Lloyd relaxation vectorised (
np.bincount) — ~24% faster world generation,
byte-identical output. Removed dead energy bookkeeping from the label placer
(and made its inner loop scan only overlapping labels); de-cubed the dungeon
extra-corridor loop.
Added
to_json/from_jsononRegion,Road,CellSummary, andWorldMapConfig
(parity with the other public dataclasses), and exportedget_theme,
theme_names,DEFAULT_THEME.
v0.27.0 — tectonic worlds + cartography pass
Additive/non-breaking. Named geographic Features + FeatureGenerator, simulated-annealing smart label placement (LabelPlacer), RegionalSVGRenderer cartography options (relief_style hillshade/hachure, scale_bar, compass), tectonic world simulation. Unblocks StoryFlow v1.4.0 F5 hex-overworld cartography.
v0.25.0 — Planet-scale worlds
Multi-continent generation reworked so a world reads as many distinct continents spread across a planet, with latitude-driven climate, ocean islands, and a one-line world preset.
Added
worldpreset — full planet in one line (continents=8, sea_level=0.64, continent_spread=0.95, mountain_density=0.7, polar_cold=0.5); best on a wide canvas, e.g.generate(240, 130).polar_coldknob (0..1) — size of the polar ice caps. Latitude sets where the cold falls; this sets how much.
Changed
- Continents are scattered swell-clusters of varying size (a few big among several small), reliably separated by open ocean; they may run to any map edge.
- Per-plate Euler-pole rotation → mountain belts vary along their length; boundaries classified convergent/divergent/transform (mountains, rifts, fault valleys).
- Oceans populated with archipelagos and curved volcanic island arcs.
- Latitude-driven temperature with polar ice caps; gentler elevation lapse (no more snow-capped equatorial peaks).
desert/arctic/tropicalpresets gainpolar_cold. - Internal cell cap raised 1500 → 8000 for planet-scale detail (small canvases unaffected).
Compatibility
polar_cold is a trailing WorldMapConfig field — positional construction and existing JSON keys unaffected (to_dict() emits one new key). Single-continent, template, and elevation-hint worlds are byte-unchanged.
Full notes: see CHANGELOG.md.
v0.24.0
Public-API hardening release. No runtime behavior changes — generated output for a given seed is unchanged.
Added
- Type information (PEP 561). Ships a
py.typedmarker, so downstream projects' type checkers (mypy, pyright) now verify their use of mapwright's public API against its annotations.
Changed
- The public contract is now pinned more tightly (no API change). The contract tests freeze the exact field names + order of every exported dataclass, and freeze the
to_dict()key schema for every serialisable type — so an internal refactor that renames/reorders/drops a public field or changes a serialised key fails CI loudly instead of silently breaking consumers.
Full Changelog: https://github.com/sligara7/mapwright/blob/main/CHANGELOG.md
v0.23.1
Bug-fix release.
Fixed
- Atlas ocean decorations no longer silently vanish.
AtlasRendererused a symbol pick purely as an existence test (discarding the drawn symbol) then independently coin-flipped betweendecoration.shipanddecoration.creature; packs supplying only one of the two slots dropped ~half their ocean decorations. The renderer now decides the slot once and stamps it, and no longer wastes RNG draws. - Terrain-shaped coastal towns no longer place their footprint in the water. The settlement minimum-core clamp ran on rays that had stopped just shy of water, pushing those vertices past the shoreline; it now applies only to rays not stopped by water.
Full Changelog: https://github.com/sligara7/mapwright/blob/main/CHANGELOG.md
v0.23.0 — Terrain-shaped settlements + non-circular continents
Continents and towns no longer come out roughly circular — both now follow the actual shape-forming process instead of a radial/convex shortcut.
Changed
- Continents are no longer roughly circular. The single-continent heightmap drops the radial distance-from-centre falloff (which forced a disk) for a per-world ocean-facing direction, an off-centre landmass seeded from two cratonic sub-plates, and a noise-warped, directional sea-frame — so coasts are ragged and the continent runs off one edge like a real margin. Multi-continent / template / hint worlds are byte-identical (
archipelago/islandsunchanged). - Organic town outlines are concave, not oval (star-shaped polar curve with arms + bays); planned
gridtowns stay convex. The settlement RNG stream changed, so exact organic-town geometry differs from 0.22.0.
Added
- Terrain-shaped settlements.
SettlementGenerator.generate(..., terrain=field)grows the footprint until it meets water or ground too high to build — a coast hugs its shore, land between lakes grows fingers, open flats spread round. - New public
world_terrain_field(world, region)derives the field from a generated map, and aTerrainFieldtype alias. Galleryterrain-townshowcase.
Full notes in CHANGELOG.md.
v0.22.0 — Settlement purpose + landmarks
Tell a town what it's for. A new purpose gives it a central landmark the main roads converge on, and biases its districts toward its role — fortress, temple town, mining camp, and more.
Added
SettlementConfig.purpose—"general"(default), or"trade","fortress","religious","harbor","extraction","transit". Anything butgeneral:- promotes the central ward to a purpose-specific landmark kind — fortress→
citadel, religious→temple, trade→market, harbor→docks, extraction→mine, transit→plaza; - focuses the main roads on that landmark (they radiate from it to the gates);
- biases the ward-kind mix toward the purpose (e.g. fortress → more garrison wards);
- is drawn with a ★ glyph over the landmark ward.
- promotes the central ward to a purpose-specific landmark kind — fortress→
- New public type
LandmarkandSettlement.landmarkfield. - New presets
fortress_town,pilgrimage_site,mining_camp, and afortress-towngallery showcase.
Compatibility
purpose="general"output is byte-identical to v0.21.0 (no landmark, unchanged ward bag and roads).Settlementserialisation bumps tomapwright/settlement@5, back-compatible — older payloads load with no landmark andpurpose="general".- Public API gains one name:
Landmark(minor bump per SemVer).
Full changelog: v0.21.0...v0.22.0
v0.21.0 — Grid streets
Towns can now be planned, not just organic. A new layout knob lays a real geometric street grid aligned to the town's long axis, with matching rectangular blocks.
Added
SettlementConfig.layout—"organic"(default) or"grid". In grid mode a town gets:- a geometric street grid aligned to the footprint's principal (long) axis — two families of parallel thoroughfares clipped to the footprint, the central line of each marked
"main"; - gates where the main streets pierce the perimeter (plus a harbour gate when coastal);
- grid-aligned lots — blocks are bisected along the grid axes, so buildings come out rectangular and street-aligned;
- walls that splice the mid-edge grid gates into the wall ring as real gatehouse gaps.
- a geometric street grid aligned to the footprint's principal (long) axis — two families of parallel thoroughfares clipped to the footprint, the central line of each marked
- New preset
grid_cityand agrid-citygallery showcase. - New reusable geometry primitive
clip_line_to_convex(Liang–Barsky line ↔ convex-polygon clipping).
Compatibility
- The default (
layout="organic") output is byte-identical to v0.20.0 — all grid logic is gated behind the new mode. - Purely additive:
layoutserialises viato_dict/json_schema(new enum-field support inSettlementConfig). No public-API names added.
Full changelog: 7d18d4e...v0.21.0
v0.20.0 — Settlement era & wealth (shanty ↔ skyscraper)
Settlement era + wealth — the shanty ↔ skyscraper axis
Two new 0..1 SettlementConfig knobs (mapwright-original, extending the age/era/wealth idea onto towns):
wealth— scales plot size (poor = cramped tiny lots ↔ rich = large estates/blocks) and the ward-kind mix (poor = slum-heavy ↔ rich = more noble/temple wards).era— sets block regularity (ancient = organic, jittered ↔ modern = near-grid rectangular blocks).
from mapwright import SettlementGenerator, SettlementConfig, SeededRNG
shanty = SettlementGenerator(SeededRNG(5)).generate(95, 95,
SettlementConfig(population=14000, wealth=0.08, era=0.3))
metro = SettlementGenerator(SeededRNG(5)).generate(95, 95,
SettlementConfig(population=14000, wealth=0.92, era=0.95))Both knobs are neutral at 0.5, so the default output is byte-identical — the shaping factors are exact identities and the neutral ward bag equals the previous fixed mix. They reshape the RNG draws' magnitudes rather than desyncing the stream.
New presets shantytown and metropolis; the README gallery shows both. Purely additive; serialises via to_dict / json_schema.
This is the imaginative-realms Layout/Geometry descriptor in concrete form.
322 tests pass; ruff clean; existing settlement gallery byte-identical.
v0.19.0 — Caller-directed terrain shape (elevation_hint)
elevation_hint — art-direct the continent's shape
RegionalTerrainGenerator.generate(..., elevation_hint=…) lets a host (or an LLM) supply the macro land/sea/elevation shape while mapwright fills in organic coastlines, erosion, rivers and climate.
mask = [[0.0, 0.0, 0.0, 0.0],
[0.0, 0.9, 0.6, 0.0], # a coarse painted heightmap
[0.0, 0.6, 0.3, 0.0],
[0.0, 0.0, 0.0, 0.0]]
terrain = RegionalTerrainGenerator(SeededRNG(7)).generate(80, 56, elevation_hint=mask)- Pass a coarse 2D grid (rows north→south, cols west→east — easy for an LLM to emit) or a callable
f(x_norm, y_norm) -> elevationover normalised [0, 1] coords. - Only relative ordering matters;
sea_levelstill sets how much floods, and the full erosion/hydrology/biome pipeline runs on top — so rivers form and coasts stay organic. - Takes precedence over
template; setedge_falloff=0to allow land at the map borders. Non-finite (NaN/inf) hints raise rather than silently poisoning the map.
This is the most on-philosophy answer to "make shapes non-circular": the caller draws the shape, mapwright does the physics (the mapgen4 hint-map idea, clean-room). Pairs naturally with the media_service partnership — an LLM can sketch a continent and mapwright realises it.
Purely additive — the default (elevation_hint=None) output is byte-identical. The README gallery shows a hint-driven crescent continent.
315 tests pass; ruff clean.