settlemaker 3.0.0 → 3.0.1

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 (55) hide show
  1. package/README.md +162 -277
  2. package/dist/output/geojson-builder.d.ts +1 -1
  3. package/dist/output/geojson-builder.js +1 -1
  4. package/dist/settlemaker.browser.js +1 -1
  5. package/docs/README.md +54 -0
  6. package/docs/api.md +175 -0
  7. package/docs/artwork-integration.md +104 -0
  8. package/docs/current-symbol-library.md +62 -42
  9. package/docs/development.md +102 -0
  10. package/docs/examples/gallery/coastal-city.png +0 -0
  11. package/docs/examples/gallery/coastal-city.svg +4253 -0
  12. package/docs/examples/gallery/coastal-village.png +0 -0
  13. package/docs/examples/gallery/coastal-village.svg +1211 -0
  14. package/docs/examples/gallery/copperline-city.png +0 -0
  15. package/docs/examples/gallery/copperline-city.svg +1139 -0
  16. package/docs/examples/gallery/copperline-village.png +0 -0
  17. package/docs/examples/gallery/copperline-village.svg +2382 -0
  18. package/docs/examples/gallery/desert-city.png +0 -0
  19. package/docs/examples/gallery/desert-city.svg +3509 -0
  20. package/docs/examples/gallery/desert-village.png +0 -0
  21. package/docs/examples/gallery/desert-village.svg +455 -0
  22. package/docs/examples/gallery/fixtures.json +449 -0
  23. package/docs/examples/gallery/temperate-city-night.png +0 -0
  24. package/docs/examples/gallery/temperate-city-night.svg +3511 -0
  25. package/docs/examples/gallery/temperate-city.png +0 -0
  26. package/docs/examples/gallery/temperate-city.svg +3511 -0
  27. package/docs/examples/gallery/temperate-village.png +0 -0
  28. package/docs/examples/gallery/temperate-village.svg +3227 -0
  29. package/docs/examples/gallery/tropical-city.png +0 -0
  30. package/docs/examples/gallery/tropical-city.svg +3508 -0
  31. package/docs/examples/gallery/tropical-village.png +0 -0
  32. package/docs/examples/gallery/tropical-village.svg +3761 -0
  33. package/docs/examples/gallery/tundra-city.png +0 -0
  34. package/docs/examples/gallery/tundra-city.svg +3509 -0
  35. package/docs/examples/gallery/tundra-village.png +0 -0
  36. package/docs/examples/gallery/tundra-village.svg +1549 -0
  37. package/docs/fmg-embed-zoom.md +54 -0
  38. package/docs/gallery.md +139 -0
  39. package/docs/geojson.md +188 -0
  40. package/docs/getting-started.md +157 -0
  41. package/docs/landscape-character-review.md +55 -0
  42. package/docs/releases/3.0.1.md +18 -0
  43. package/docs/scene-schema.md +160 -0
  44. package/docs/schema-v3.md +38 -0
  45. package/docs/skins.md +52 -0
  46. package/docs/test-urls.md +39 -0
  47. package/docs/url-api.md +252 -0
  48. package/docs/water-context-v1.md +384 -0
  49. package/package.json +15 -8
  50. package/scripts/check-docs.mjs +106 -0
  51. package/scripts/generate-copperline.mjs +255 -0
  52. package/scripts/generate-examples.mjs +54 -0
  53. package/scripts/render-copperline.mjs +36 -0
  54. package/src/output/geojson-builder.ts +1 -1
  55. package/symbols/copperline/README.md +10 -3
@@ -0,0 +1,160 @@
1
+ # City scene and rendering contract
2
+
3
+ `Scene` version **2** is the semantic rendering representation of the **city**
4
+ planner. Villages use `VillageModel` and `renderVillage`; they do not pass through
5
+ `buildScene`. Use [GeoJSON](geojson.md) for exported feature data from both planners,
6
+ and [skins](skins.md) for portable artwork that works in both.
7
+
8
+ ```text
9
+ City: Model → buildScene → Scene v2 → assembleSvg → SVG
10
+ Village: VillageModel → renderVillage → SVG
11
+ ```
12
+
13
+ The scene stores geometry, semantic kinds, saved glyph selections and material
14
+ variant indices. It does not contain SVG fragment bodies or a complete theme.
15
+ `assembleSvg` supplies the artwork and appearance. The complete declarations live
16
+ in [src/scene/scene.ts](../src/scene/scene.ts).
17
+
18
+ ## Render an existing city model
19
+
20
+ This complete example uses the high-level city generator to retain its coastal
21
+ shift and degradation behaviour, then renders the saved model in another palette:
22
+
23
+ ```js
24
+ import { generateFromBurg, buildScene, assembleSvg, PALETTES } from 'settlemaker';
25
+
26
+ const burg = {
27
+ name: 'Thornwall', population: 5000, biome: 'temperate',
28
+ port: false, citadel: true, walls: true, plaza: true,
29
+ temple: true, shanty: false, capital: false,
30
+ roadBearings: [20, 145, 270],
31
+ };
32
+ const result = generateFromBurg(burg, { seed: 42 });
33
+ const scene = buildScene(result.model, { shift: result.originShift, padding: 20 });
34
+ const svg = assembleSvg(scene, { palette: PALETTES.night });
35
+ console.log(scene.version, svg.length);
36
+ ```
37
+
38
+ `generateSvg(model, options)` is a convenience wrapper around those two stages.
39
+ When rerendering a model from `generateFromBurg`, pass its `originShift` if you
40
+ want the same output coordinate frame. When generating SVG and GeoJSON separately,
41
+ use the same shift and padding for both.
42
+
43
+ For direct control over city generation:
44
+
45
+ ```js
46
+ import { mapToGenerationParams, Model, generateSvg, generateGeoJson } from 'settlemaker';
47
+
48
+ // burg is the complete object from the preceding example.
49
+ const params = mapToGenerationParams(burg, 42);
50
+ const model = new Model(params).generate();
51
+ const svg = generateSvg(model);
52
+ const geojson = generateGeoJson(model);
53
+ ```
54
+
55
+ `GenerationParams` is a TypeScript interface. It cannot be called with `new`.
56
+ This direct path does not reproduce the two-pass coastal output translation
57
+ performed by `generateFromBurg`; use that high-level function when you want it.
58
+
59
+ ## Scene fields
60
+
61
+ | Field | Meaning |
62
+ | --- | --- |
63
+ | `version` | Literal 2. |
64
+ | `seed`, `population`, optional `name`, `biome` | Generating context. |
65
+ | `bounds` | Local output bounds. |
66
+ | `metersPerUnit` | Optional city scale estimate for rendering, not a survey. |
67
+ | `buildingCapacity` | Optional ordinary-building budget accounting. |
68
+ | `layers` | The geometry and instance arrays described below. |
69
+
70
+ | Layer | Contents |
71
+ | --- | --- |
72
+ | `water` | `{rings, synthetic}`; even-odd output rings retain islands. |
73
+ | `fields` | Polygon `ring`, `angleDeg`, optional native `glyph` and `hatch`. Pattern pitch is independent of parcel size. |
74
+ | `furrows` | Deprecated array; current scenes leave it empty and use field direction instead. |
75
+ | `greens` | Polygon rings with optional planned paths and path width. |
76
+ | `vegetation` | Position, glyph/legacy `kind`, scale and rotation. |
77
+ | `symbols` | Glyph `id`, position, scale, optional `scaleY`, `buildingId`, `materialVariant`, rotation, and `zBand`. |
78
+ | `roads` | Paths with `kind: 'artery' | 'road' | 'alley'` and optional width. |
79
+ | `buildings` | Polygon, semantic ward `kind`, `landmark`, optional `id` and `glyphBacked`. |
80
+ | `piers` | Polygon rings. |
81
+ | `walls` | Polylines, towers, gates and `large`; optional curtain/rubble/palisade material. Gates retain route IDs. |
82
+ | `bridges` | Optional on v2 scenes; bank-to-bank path, width and ID. |
83
+
84
+ An instance's scale gives its art-box size in scene units; artwork coordinates
85
+ come from the selected glyph's view box and anchor. Preserve non-square boxes and
86
+ `scaleY`. Native field tiles may have a 32-unit box, structure slots commonly 64,
87
+ and downloadable authoring assets can use other dimensions. There is no universal
88
+ 2×2 authoring rule for current glyphs.
89
+
90
+ ## Appearance and artwork
91
+
92
+ `assembleSvg(scene, options)` accepts `palette`, partial `theme`, `skin`,
93
+ `skinBiome`, `assetSet`, `symbols` and `clipId`. `generateSvg` also accepts scene
94
+ `padding` and `shift`.
95
+
96
+ The default asset set is **`SETTLEMENT_SET`**, containing the current native biome
97
+ artwork. `REFINED_SET` and `SCHEMATIC_SET` remain explicit lower-level alternatives.
98
+ `assetSetFor` returns the current default. `paletteForBiome` currently supplies
99
+ its palette fallback; the artwork and material selection provide much of the
100
+ visible biome variation. Do not assume each biome corresponds to a different
101
+ named city palette.
102
+
103
+ For an artist-facing integration, prefer `createSkin` and the [skin contract](skins.md).
104
+ It validates portable JSON, allowed SVG fragments and exact slot names without
105
+ requiring changes to the asset lookup code. An explicit city `assetSet` overrides
106
+ a skin's artwork. Skin colours/biomes and renderer overrides follow the precedence
107
+ in the skin guide.
108
+
109
+ `AssetSet` remains a lower-level API for trusted code. It has a name, legacy
110
+ semantic `symbols`, optional `patterns`, `glyphs`, placement `manifest` and
111
+ `refined` styling flag. Its legacy symbols use a unit box, while `glyphs` carry
112
+ explicit view boxes and anchors. Do not apply the legacy unit-box convention to
113
+ portable skin glyphs.
114
+
115
+ ## SVG styling and geometry
116
+
117
+ Current SVGs combine group CSS, material custom properties and presentation
118
+ attributes inside native or procedural artwork. They are not wholly styled by
119
+ one CSS block, and changing a group fill does not recolour every roof or tree.
120
+ Use documented theme/token controls instead of rewriting arbitrary SVG attributes.
121
+
122
+ City groups include fields, greens, water, roads, buildings, landmarks, shadows,
123
+ placed symbols/marks, vegetation and walls. Some groups are omitted when empty;
124
+ procedural artwork can add nested groups, masks and patterns. These group names
125
+ are not the village renderer's DOM contract. For city building identity, emitted
126
+ `data-building-id` attributes connect applicable polygons/symbols to GeoJSON IDs.
127
+ Use GeoJSON for semantic feature queries rather than inferring a feature solely
128
+ from its colour or DOM position.
129
+
130
+ `symbols: false` hides placed city symbols and overlay marks. It leaves vegetation
131
+ and actual building polygons available. It is not a universal switch to remove
132
+ all embedded artwork.
133
+
134
+ ## Embedding SVG
135
+
136
+ The `data-bg="paper"` rectangle is used by the tile cropper. Preserve it when
137
+ post-processing. Village roots also declare `data-px-per-metre`; use it to retain
138
+ their physical scale through tiling.
139
+
140
+ Generated SVG IDs are scoped to a document. When displaying several maps, prefer
141
+ separate `<img>` documents (including SVG Blob URLs), `<object>` elements or
142
+ iframes. A unique city `clipId` only changes selected clipping/pattern IDs; it
143
+ does not namespace every glyph, CSS selector or village ID. It is insufficient
144
+ by itself to make multiple inline maps independent. A custom inline compositor
145
+ must consistently namespace IDs, references and applicable styles.
146
+
147
+ SVG output is self-contained for artwork. A browser host is responsible for
148
+ responsive sizing, pan/zoom, downloads and visible errors. Those behaviours are
149
+ not implemented by importing the generator alone.
150
+
151
+ ## Evolution and saved scenes
152
+
153
+ Optional fields such as `layers.bridges`, field `glyph`, wall `material` and symbol
154
+ `materialVariant` extend Scene v2. Consumers should handle their absence in older
155
+ saved scenes. Breaking changes to existing field meaning or structure require a
156
+ scene version change.
157
+
158
+ A saved scene is not a complete reproduction package: retain the generator
159
+ version, skin definition and renderer options too. A `SettlementSkin` runtime
160
+ handle must be recreated from JSON in the module instance doing the rendering.
@@ -0,0 +1,38 @@
1
+ # Historical GeoJSON schema v3
2
+
3
+ This page records the city-output transition introduced in release 0.4.0.
4
+ **Current output uses schema 4.** Use the [current GeoJSON reference](geojson.md)
5
+ for new integrations, including village coordinates, road widths and diagnostics.
6
+ Do not gate current output on `schema_version === 3`.
7
+
8
+ ## What changed in v3
9
+
10
+ City output gained POI Point features, building and street IDs, ID-prefix
11
+ metadata, and the hamlet/town POI density marker. Existing city properties such
12
+ as `wardType` and `streetType` retained camelCase; additions such as `building_id`
13
+ and `poi_id` used snake_case. That mixture remains deliberate in current output.
14
+
15
+ Later city releases added symbol-backed floating wells, markets and mills, then
16
+ capacity accounting and other optional schema-4 fields. Later village releases
17
+ introduced a different planner and its own feature properties. The historic
18
+ city-only POI and flat-street descriptions must not be applied to every village.
19
+
20
+ ## Stable-ID contract
21
+
22
+ IDs are deterministic for identical inputs and a fixed generator version.
23
+ They are opaque and are not guaranteed stable across upgrades. See
24
+ [current identity and persistence](geojson.md#ids-and-persistence).
25
+
26
+ ## Village road cross-sections
27
+
28
+ The documentation formerly appended here now lives in the
29
+ [current village road section](geojson.md#village-road-cross-sections), including
30
+ corridor widths, painted widths, setbacks, bridge decks and route provenance.
31
+
32
+ ## Migration for consumers
33
+
34
+ For 3.0.x, check schema **4**, inspect `properties.layer` and geometry types,
35
+ and accept optional fields your application does not need. Branch for the
36
+ [village/city differences](geojson.md), especially POIs, greens, gate features,
37
+ coordinates and IDs. The package version, scene version and URL envelope version
38
+ are separate discriminators; see the [documentation index](README.md#contract-versions).
package/docs/skins.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Authoring settlement skins
2
2
 
3
+ This is the portable **skin format 1** contract for SettleMaker 3.0.x. Start here
4
+ to create a personalised setting; the older `AssetSet` API is for trusted city
5
+ rendering code and is not required to distribute a skin.
6
+
3
7
  A skin is a versioned JSON document containing SVG artwork, material colours,
4
8
  and optional named biomes. The same skin works with villages and cities in Node
5
9
  and the browser. Load it once with `createSkin`, then pass it per request:
@@ -7,6 +11,13 @@ and the browser. Load it once with `createSkin`, then pass it per request:
7
11
  ```ts
8
12
  import { createSkin, generateSettlement } from 'settlemaker';
9
13
 
14
+ const burg = {
15
+ name: 'Moonfall', population: 300,
16
+ port: false, citadel: false, walls: false, plaza: true,
17
+ temple: true, shanty: false, capital: false,
18
+ roadBearings: [18, 142, 267],
19
+ };
20
+
10
21
  // Browser example. Applications choose where to store/host their skin files.
11
22
  const response = await fetch('/skins/moon-glass.skin.json');
12
23
  if (!response.ok) throw new Error('Could not load skin');
@@ -22,6 +33,9 @@ In Node, pass `JSON.parse(await readFile(path, 'utf8'))` to `createSkin`.
22
33
  There is no global registry, automatic network access, or executable plugin code.
23
34
  Keep the original JSON to save or distribute the skin; the returned skin is an
24
35
  immutable runtime handle. Invalid definitions throw an error naming the property.
36
+ Create the handle in the same module instance that generates or renders the map.
37
+ For a worker, another independently loaded bundle, or saved data, transfer the
38
+ definition JSON and call `createSkin` there. There is no runtime-handle wire format.
25
39
 
26
40
  Start with [Moon Glass](examples/moon-glass.skin.json), a small working example
27
41
  that replaces snowy dwellings with crystal homes and colours their surroundings.
@@ -157,3 +171,41 @@ Skins are currently a library API. A hosting application can add a picker or loa
157
171
  its own curated files; the URL codec does not serialize skin artwork or fetch skin
158
172
  URLs. Distribute your original artwork with your chosen licence information. For
159
173
  artwork derived from the bundled symbols, consult [symbols/LICENSE](../symbols/LICENSE).
174
+
175
+ ## Build and review your own skin
176
+
177
+ 1. Choose a base biome whose terrain behaviour fits the setting. Begin with a
178
+ partial skin so you can evaluate the style before drawing a complete set.
179
+ 2. Inspect `SKIN_SLOTS` and the current source SVG for each replacement. Retain the
180
+ slot's view box, orientation and painted extents. Export the inner shapes and,
181
+ for structures, a matching silhouette.
182
+ 3. Put replacements under their exact IDs. Biome variants are separate entries:
183
+ a generic house replacement does not automatically replace every snowy or
184
+ desert house. Cover each category that appears in your target biomes.
185
+ 4. Add tokens and the separate `village`/`city` theme overrides. Render both engines
186
+ with the same loaded skin and try more than one seed and population.
187
+ 5. Validate using `createSkin`, then inspect SVG and raster output for seams,
188
+ clipping, shadows and overlap. Schema validation alone cannot establish visual
189
+ compatibility with a placement footprint.
190
+ 6. Distribute the JSON with a README, preview images and your artwork's license and
191
+ credits in adjacent files. Do not add unsupported executable hooks or invent
192
+ top-level schema fields for license text. Record the generator version used for
193
+ testing and the biomes/categories you intentionally cover.
194
+
195
+ The [gallery](gallery.md) demonstrates real library output. Its
196
+ [generator script](../scripts/generate-examples.mjs) shows complete inputs for both
197
+ planners. For original alternative artwork and regeneration commands, inspect
198
+ [Copperline](../symbols/copperline/README.md). Its GPL-3.0-only terms are separate
199
+ from the default collections' rendered-output exception.
200
+
201
+ ## Common mistakes
202
+
203
+ | Symptom | Check |
204
+ | --- | --- |
205
+ | Some default buildings remain | Replacement IDs must match exact runtime slots, including biome variants. Partial skins deliberately inherit omissions. |
206
+ | A named biome renders as temperate | Pass the loaded skin on that generation request and use a defined key/alias. Unknown names retain the normal fallback. |
207
+ | A replacement overlaps its neighbour | Check painted extents, not just whether the drawing fits inside the SVG view box. Placement footprints cannot be changed by skin format 1. |
208
+ | The old shadow remains or the replacement has none | Supply a matching `sil`; an omitted silhouette means no shadow for that replaced glyph. |
209
+ | A worker rejects a skin handle | Send the original JSON and recreate the handle inside that worker's module instance. |
210
+ | A road or bridge ignores an SVG replacement | Some features are procedural geometry, outside the slot catalogue. Use the exposed renderer controls where available. |
211
+ | An iframe ignores a skin URL | The URL codec has no skin parameter. Load skins through your application's library integration. |
@@ -0,0 +1,39 @@
1
+ # Reproducible example URLs
2
+
3
+ The [current gallery](gallery.md) is the public example set for 3.0.x. Its images
4
+ are generated with `generateSettlement`, so population ≤1,000 uses the village
5
+ planner instead of forcing small inputs through the city generator.
6
+
7
+ Each entry includes its population, seed, full SVG and (where supported) a hosted
8
+ preview URL. [fixtures.json](examples/gallery/fixtures.json) records the complete
9
+ burg inputs, generator version and skin choice. Use those inputs with your pinned
10
+ npm package for exact local reproduction; a hosted URL uses the website's deployed
11
+ build and can change appearance when that site updates.
12
+
13
+ ## Generate links
14
+
15
+ Use the public `encodeBurgParam` helper described in the [URL contract](url-api.md).
16
+ Supply the complete input and an explicit seed. Route IDs and measured water
17
+ require the compressed input tier; the flat tier cannot preserve them.
18
+
19
+ Copperline examples are direct library calls. The URL protocol does not transport
20
+ skins, so the gallery links those examples to their generated SVG and skin JSON
21
+ instead of presenting a URL that would silently show default artwork.
22
+
23
+ ## Regenerate and review
24
+
25
+ ```sh
26
+ npm ci
27
+ npm run docs:images
28
+ npm run docs:check
29
+ ```
30
+
31
+ The image command regenerates PNG and SVG files together and rewrites the gallery
32
+ and fixture manifest. Review the actual maps before committing them. PNGs use the
33
+ SVG's supplied CSS-variable fallbacks for rasterizer compatibility.
34
+
35
+ The generator script is [scripts/generate-examples.mjs](../scripts/generate-examples.mjs).
36
+ Older development review fixtures in `scripts/generate-test-urls.ts` and previous
37
+ image files are historical aids, not the current documentation contract or a list
38
+ of unresolved defects. Current behaviour and limits are documented in the
39
+ [API](api.md), [GeoJSON reference](geojson.md) and [water contract](water-context-v1.md).
@@ -0,0 +1,252 @@
1
+ # URL adapter contract
2
+
3
+ This guide describes the **3.0.x library's URL codec** and the hosting contract
4
+ for a renderer at `https://settlemaker.com/fmg`. The URL envelope version remains
5
+ **1**. The npm library and hosted website have separate release cycles; a package
6
+ publication alone does not establish which generator a website is running.
7
+
8
+ Use the [library API](api.md) when your application generates maps itself. Use a
9
+ URL adapter when a host such as Azgaar's Fantasy Map Generator embeds a separately
10
+ deployed renderer. There is no HTTP JSON generation endpoint or iframe result
11
+ message protocol in this contract.
12
+
13
+ ## Endpoints and responsibility
14
+
15
+ | Path | Role |
16
+ | --- | --- |
17
+ | `/fmg` | Embedded renderer receiving a burg URL. |
18
+ | `/` | Human-facing application; do not use it as the burg-rendering endpoint. |
19
+ | `/symbols` | Artwork browsing, outside the input protocol. |
20
+
21
+ The host supplies the URL and owns its surrounding UI. The renderer parses it,
22
+ generates SVG and displays success, warnings or a visible error. Hosts cannot
23
+ read a cross-origin iframe's model, GeoJSON or JavaScript result. Direct library
24
+ consumers can read those results without an iframe.
25
+
26
+ ## Build a complete link
27
+
28
+ ```js
29
+ import { encodeBurgParam } from 'settlemaker';
30
+
31
+ const burg = {
32
+ name: 'Ashford', population: 300, biome: 'temperate',
33
+ port: false, citadel: false, walls: false, plaza: true,
34
+ temple: true, shanty: false, capital: false,
35
+ roadBearings: [
36
+ { bearing_deg: 36, kind: 'main', route_id: 'r17', through: true },
37
+ { bearing_deg: 209, kind: 'main', route_id: 'r17', through: true },
38
+ { bearing_deg: 117, kind: 'town', route_id: 'r23' },
39
+ ],
40
+ };
41
+ const url = new URL('https://settlemaker.com/fmg');
42
+ url.searchParams.set('i', await encodeBurgParam(burg, 42));
43
+ console.log(url.href); // use as the iframe src or external-open link
44
+ ```
45
+
46
+ The codec uses Web Platform globals, including
47
+ `CompressionStream('deflate-raw')` and `DecompressionStream('deflate-raw')`.
48
+ Verify support in the host environment. The library provides no compression
49
+ polyfill. The full [input reference](api.md#burg-input) is shared with direct
50
+ library calls; always supply all required flags explicitly.
51
+
52
+ ## Compressed payload
53
+
54
+ `i=` contains this envelope:
55
+
56
+ ```json
57
+ {
58
+ "v": 1,
59
+ "burg": {
60
+ "name": "Ashford",
61
+ "population": 300,
62
+ "port": false,
63
+ "citadel": false,
64
+ "walls": false,
65
+ "plaza": true,
66
+ "temple": true,
67
+ "shanty": false,
68
+ "capital": false
69
+ },
70
+ "seed": 42
71
+ }
72
+ ```
73
+
74
+ Encoding is JSON → UTF-8 → raw DEFLATE → unpadded base64url. Raw DEFLATE has no
75
+ zlib/gzip header. `encodeBurgParam` and `decodeBurgParam` implement this envelope;
76
+ `encodeJsonParam` and `decodeJsonParam` provide the generic JSON codec used by
77
+ presentation overrides.
78
+
79
+ `v` must equal 1. `seed` is optional; otherwise generation derives it from the
80
+ burg name. The decoder checks the envelope, a plausible name/population and any
81
+ water context. It is not complete validation of every burg field. Validate
82
+ untrusted integration data before calling generation.
83
+
84
+ `i=` takes precedence over **all flat data fields**, including a flat `seed`.
85
+ A malformed `i=` is an error, not permission to retry with flat fields.
86
+ Presentation parameters remain separate and apply after input decoding.
87
+
88
+ ## Roads
89
+
90
+ Send one record per **actual land approach**, with its independently measured
91
+ clockwise bearing from north. Keep both sides of a continuing route even when
92
+ they are not 180 degrees apart; use the same `route_id` for those two records.
93
+ `through: true` is a hint and never creates an exit.
94
+
95
+ | Source meaning | `group` | `kind` |
96
+ | --- | --- | --- |
97
+ | Known land-road class | `roads` | `royal`, `main`, `market`, `town`, `local` |
98
+ | Known trail class | `trails` | `trail`, `footpath` |
99
+ | Road group without a class | `roads` | `road` |
100
+ | Trail group without a class | `trails` | `foot` |
101
+ | Sea, air or trade-only network without a land approach | — | Exclude from land approaches. |
102
+
103
+ Do not transmit UI labels such as `"roads / royal"` as a kind. `local` is accepted,
104
+ but an adapter should not invent it for missing source classes. `relief` and
105
+ `followsRiver` carry optional city growth hints. See [road inputs](api.md#roads).
106
+
107
+ Village approaches preserve source provenance as they join shared internal
108
+ streets. Drawn village street classes can change to `town`, `local` or `footpath`;
109
+ a small roadside hamlet can retain a major through road. City outputs use their
110
+ legacy road/foot categories and gate matching. Do not assume identical feature
111
+ counts or names in both engines.
112
+
113
+ An explicit `roadBearings: []` means no external approaches. Omitted approaches
114
+ also produce none in villages; the city planner retains its legacy random-gate
115
+ fallback when the field is absent.
116
+
117
+ ## Water and physical units
118
+
119
+ The full [water-context v1 specification](water-context-v1.md) remains the
120
+ normative contract for measured **villages, population 1–1,000**. A city input with
121
+ `waterContext` is rejected with `water-context-unsupported-engine`; the host must
122
+ not strip the object and silently reinterpret its metre geometry as city units.
123
+
124
+ | Input | Village | City |
125
+ | --- | --- | --- |
126
+ | `coastlineGeometry` | Filled polygons in burg-local metres | Filled polygons in city-local mesh units |
127
+ | `rivers` | Centreline, positive `widthM`, optional `meander: false` | Not consumed by city generation |
128
+ | `oceanBearing` | Generated coast when explicit/measured inputs do not supersede it | Legacy generated coast fallback |
129
+ | `waterContext` | Validated survey/unknown-unit mode | Rejected |
130
+
131
+ Positive x points east and positive y south. Do not send geographic coordinates
132
+ or assume city-local units are metres. `port` controls infrastructure, not water
133
+ visibility. A `coastal` biome chooses artwork but does not itself add an ocean.
134
+
135
+ A nonempty `coastlineGeometry` array represents **filled areas**, not bank lines.
136
+ Include all relevant water polygons and preserve narrow channels and bends. For
137
+ village rivers, supply a centreline with width or a filled polygon, not duplicate
138
+ representations. `followsRiver` alone creates no water.
139
+
140
+ For measured water, carry summaries, polygon associations, declared geometry
141
+ error and survey coverage together. An empty measured survey suppresses the
142
+ legacy coast; unknown units remain a diagnostic state. URL compression/length
143
+ problems must not fall back to a flat URL that loses measured-water semantics.
144
+ Simplify only within the declared physical error budget, or show an encoding error.
145
+ There is no automatic iframe resurvey protocol.
146
+
147
+ ## Flat parameters
148
+
149
+ The flat tier is convenient for manual examples:
150
+
151
+ ```text
152
+ https://settlemaker.com/fmg?name=Ashford&pop=300&seed=2&biome=temperate&plaza=1&temple=1&roads=18:main,142:town,267:local
153
+ ```
154
+
155
+ | Parameter | Parsed meaning |
156
+ | --- | --- |
157
+ | `name` | Name; default derived from seed/population if omitted. |
158
+ | `pop` | Finite population; defaults to 300. |
159
+ | `seed` | Optional numeric seed. |
160
+ | `port`, `citadel`, `walls`, `plaza`, `temple`, `shanty`, `capital`, `trade` | True for `1` or `true`; otherwise false. |
161
+ | `biome` | Input biome name. |
162
+ | `urbanDensity`, `coreCapacity` | Optional positive numbers. |
163
+ | `oceanBearing` | Optional finite compass bearing. |
164
+ | `harbourSize` | `large` or `small`. |
165
+ | `roads` | Comma-separated `bearing[:class[:through]]` records. |
166
+
167
+ `roads=36:main:through,209:main:through,117:town` supplies all three approaches.
168
+ Bearings wrap into `[0, 360)`; default class is `main`. Valid flat classes are the
169
+ seven land classes in `ROUTE_CLASS_ORDER`; legacy/group aliases are not accepted
170
+ by this flat syntax. Invalid classes or continuation tokens throw a `roads` error.
171
+ Flat records cannot carry route IDs, relief, river following or measured water.
172
+
173
+ With no data fields, `parseSettlementUrl` chooses a demo input and seed. That
174
+ includes a URL containing only presentation fields. A URL with explicit input and
175
+ a resolved seed is reproducible within the same deployed generator version; the
176
+ bare demo URL intentionally is not.
177
+
178
+ ## Presentation
179
+
180
+ | Parameter | Used by | Meaning |
181
+ | --- | --- | --- |
182
+ | `theme` | City | Named palette. |
183
+ | `style` | City | Compressed JSON with partial `RenderTheme` overrides. |
184
+ | `villageTheme` | Village | One of `temperate`, `desert`, `tundra`, `tropical`, `coastal`. |
185
+
186
+ City palette keys are `default`, `classic`, `parchment`, `blueprint`, `bw`, `ink`,
187
+ `night`, `ancient`, `colour`, `simple`. `default` aliases `parchment`; `classic` selects the
188
+ original classic palette. The parser returns the name; a renderer must validate it against
189
+ `PALETTES` and display an unknown-palette error itself.
190
+
191
+ `style` accepts hex colour fields, nullable water colours, finite theme numbers
192
+ and a finite `{dx, dy}` shadow offset. Unknown fields/wrong value shapes are dropped
193
+ by `sanitizeThemeOverrides`. Accepted material keys include `smInk`, `smStone`,
194
+ `smTimber`, `smVoid`, `smCanopy1`, `smCanopy2`, alongside the geometry style fields in
195
+ [RenderTheme](../src/output/render-theme.ts). This is not arbitrary CSS.
196
+
197
+ `villageTheme` changes ground, water, shadows and material tokens without changing
198
+ the input biome's dwelling/landscape selection. An invalid name is a
199
+ `UrlCodecError` with reason `villageTheme`, even when the city engine would run.
200
+ The city presentation fields have no effect on the village renderer, and vice versa.
201
+
202
+ **Skins are a direct library feature.** There is no `skin=` URL option, remote skin
203
+ fetch, or embedded skin definition in this protocol. A host that offers a skin
204
+ picker must explicitly load a definition and pass the resulting handle to the
205
+ library API. There is also no `symbols=` or `fit=` URL option in this parser.
206
+
207
+ ## Implementing a renderer
208
+
209
+ ```js
210
+ import {
211
+ parseSettlementUrl, generateSettlement, PALETTES, villageThemeFor,
212
+ } from 'settlemaker';
213
+
214
+ // In a browser page:
215
+ const parsed = await parseSettlementUrl(new URLSearchParams(location.search));
216
+ const palette = parsed.paletteName === undefined ? undefined : PALETTES[parsed.paletteName];
217
+ if (parsed.paletteName !== undefined && !Object.hasOwn(PALETTES, parsed.paletteName)) {
218
+ throw new Error(`Unknown palette: ${parsed.paletteName}`);
219
+ }
220
+ const result = generateSettlement(parsed.burg, {
221
+ seed: parsed.seedOverride,
222
+ svg: { palette, theme: parsed.themeOverrides },
223
+ village: parsed.villageThemeName
224
+ ? { theme: villageThemeFor(parsed.villageThemeName) }
225
+ : undefined,
226
+ });
227
+ // Display result.svg and any result.waterContextResult diagnostics.
228
+ ```
229
+
230
+ Wrap parsing/generation in your page's error handler. `UrlCodecError.reason` can
231
+ be `base64`, `inflate`, `json`, `version`, `shape`, `villageTheme` or `roads`.
232
+ Fatal water errors use `WaterContextError.waterContextResult`; successful measured
233
+ results can still contain warnings. Keep errors and warnings visible without
234
+ hover or interaction, including inside pointer-disabled previews.
235
+
236
+ The library does not render diagnostic cards for you. An iframe host must provide
237
+ those in its page, and must not assume a source-map popup or console error is a
238
+ usable explanation for an embedded user. See [embedding and zoom](fmg-embed-zoom.md).
239
+
240
+ ## Deployment and compatibility
241
+
242
+ Pin the renderer to a tested library release, deploy its diagnostics alongside it,
243
+ then enable corresponding measured-water inputs in the sending application.
244
+ Validate preview and external-open links together. Do not infer website support
245
+ from the npm `latest` tag.
246
+
247
+ Payload v1 evolves through optional additions. Breaking changes to the envelope
248
+ require a new version; decoders reject an unsupported version. Geometry changes
249
+ can occur without a URL or GeoJSON version bump, so save the package/build version
250
+ with any cached preview or generated IDs.
251
+
252
+ [Current generated fixtures and preview links](gallery.md) · [Input API](api.md)