hoi4-agent-tools 4.3.0 → 4.4.0

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 (110) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/dist/hoi4_agent_tools/core/bounded-source.js +31 -9
  3. package/dist/hoi4_agent_tools/core/configuration.js +11 -0
  4. package/dist/hoi4_agent_tools/core/operation-context.js +17 -1
  5. package/dist/hoi4_agent_tools/core/temporary-directories.js +7 -2
  6. package/dist/hoi4_agent_tools/core/transaction-limits.js +18 -4
  7. package/dist/hoi4_agent_tools/core/transactions.js +23 -4
  8. package/dist/hoi4_agent_tools/core/workspace-json.js +54 -0
  9. package/dist/hoi4_agent_tools/core/workspace.js +42 -6
  10. package/dist/hoi4_agent_tools/map/appearance-validation.js +1110 -0
  11. package/dist/hoi4_agent_tools/map/bmp.js +9 -1
  12. package/dist/hoi4_agent_tools/map/border-quality-input.js +209 -0
  13. package/dist/hoi4_agent_tools/map/border-quality.js +1180 -0
  14. package/dist/hoi4_agent_tools/map/colormap.js +128 -0
  15. package/dist/hoi4_agent_tools/map/geography/geometry.js +284 -0
  16. package/dist/hoi4_agent_tools/map/geography/index.js +6 -0
  17. package/dist/hoi4_agent_tools/map/geography/model.js +196 -0
  18. package/dist/hoi4_agent_tools/map/geography/preparation.js +120 -0
  19. package/dist/hoi4_agent_tools/map/geography/preview.js +647 -0
  20. package/dist/hoi4_agent_tools/map/geography/raster.js +253 -0
  21. package/dist/hoi4_agent_tools/map/geography/resolve.js +1467 -0
  22. package/dist/hoi4_agent_tools/map/model.js +15 -2
  23. package/dist/hoi4_agent_tools/map/operations.js +111 -8
  24. package/dist/hoi4_agent_tools/map/partition-fields.js +398 -0
  25. package/dist/hoi4_agent_tools/map/partition-geography.js +113 -0
  26. package/dist/hoi4_agent_tools/map/partition-graph.js +165 -0
  27. package/dist/hoi4_agent_tools/map/partition-groups.js +164 -0
  28. package/dist/hoi4_agent_tools/map/partition-provinces.js +727 -0
  29. package/dist/hoi4_agent_tools/map/partition-tuning.js +101 -0
  30. package/dist/hoi4_agent_tools/map/partition-watershed.js +252 -0
  31. package/dist/hoi4_agent_tools/map/partition.js +163 -0
  32. package/dist/hoi4_agent_tools/map/reference-import.js +333 -0
  33. package/dist/hoi4_agent_tools/map/reference-preparation.js +138 -5
  34. package/dist/hoi4_agent_tools/map/reference-sheets.js +231 -0
  35. package/dist/hoi4_agent_tools/map/reference-world.js +104 -13
  36. package/dist/hoi4_agent_tools/map/relief/checks.js +281 -0
  37. package/dist/hoi4_agent_tools/map/relief/fields.js +15 -0
  38. package/dist/hoi4_agent_tools/map/relief/full-resolution.js +133 -0
  39. package/dist/hoi4_agent_tools/map/relief/grid.js +190 -0
  40. package/dist/hoi4_agent_tools/map/relief/hydrology.js +1315 -0
  41. package/dist/hoi4_agent_tools/map/relief/index.js +27 -0
  42. package/dist/hoi4_agent_tools/map/relief/land.js +110 -0
  43. package/dist/hoi4_agent_tools/map/relief/macro-relief.js +535 -0
  44. package/dist/hoi4_agent_tools/map/relief/math.js +93 -0
  45. package/dist/hoi4_agent_tools/map/relief/river-raster.js +680 -0
  46. package/dist/hoi4_agent_tools/map/relief/terrain-synthesis.js +304 -0
  47. package/dist/hoi4_agent_tools/map/render.js +36 -0
  48. package/dist/hoi4_agent_tools/map/service.js +191 -16
  49. package/dist/hoi4_agent_tools/map/tool-operations.js +208 -2
  50. package/dist/hoi4_agent_tools/map/validation.js +9 -0
  51. package/dist/hoi4_agent_tools/map/water.js +227 -0
  52. package/dist/hoi4_agent_tools/map/world-context.js +24 -1
  53. package/dist/hoi4_agent_tools/map/world-edge.js +335 -0
  54. package/dist/hoi4_agent_tools/map/world-noise.js +34 -0
  55. package/dist/hoi4_agent_tools/map/world-partition.js +119 -0
  56. package/dist/hoi4_agent_tools/map/world-provinces.js +131 -56
  57. package/dist/hoi4_agent_tools/map/world.js +462 -124
  58. package/dist/hoi4_agent_tools/mcp/tools/map.js +69 -1
  59. package/dist/hoi4_agent_tools/reference/operations.js +3 -2
  60. package/dist/hoi4_agent_tools/schemas/geography.js +325 -0
  61. package/dist/hoi4_agent_tools/schemas/map-requests.js +20 -2
  62. package/dist/hoi4_agent_tools/schemas/map.js +209 -2
  63. package/dist/hoi4_agent_tools/schemas/reference-sheets.js +92 -0
  64. package/dist/hoi4_agent_tools/version.js +1 -1
  65. package/docs/configuration.md +17 -14
  66. package/docs/map.md +258 -15
  67. package/docs/tools.md +4 -4
  68. package/docs/troubleshooting.md +11 -11
  69. package/npm-shrinkwrap.json +109 -91
  70. package/package.json +3 -3
  71. package/schemas/configuration.schema.json +65 -58
  72. package/schemas/continuous-focus-palette.schema.json +1 -1
  73. package/schemas/custom-weighted-pool.schema.json +1 -1
  74. package/schemas/decision-inspect-input.schema.json +1 -1
  75. package/schemas/error-log-input.schema.json +1 -1
  76. package/schemas/event-feature-manifest.schema.json +1 -1
  77. package/schemas/focus-plan.schema.json +1 -1
  78. package/schemas/focus-planning-sidecar.schema.json +1 -1
  79. package/schemas/geography-model.schema.json +1148 -0
  80. package/schemas/gui-animation-source.schema.json +1 -1
  81. package/schemas/gui-helper.schema.json +5 -11
  82. package/schemas/gui-scenario.schema.json +1 -1
  83. package/schemas/helper-expansion-request.schema.json +1 -1
  84. package/schemas/helper-expansion-summary.schema.json +1 -1
  85. package/schemas/impact-inspect-input.schema.json +1 -1
  86. package/schemas/map-operation.schema.json +2156 -259
  87. package/schemas/mechanic-test-input.schema.json +1 -1
  88. package/schemas/mod-index-input.schema.json +1 -1
  89. package/schemas/operation-result.schema.json +1 -1
  90. package/schemas/package-check-input.schema.json +1 -1
  91. package/schemas/package-manifest.schema.json +1 -1
  92. package/schemas/probability-analysis-result.schema.json +25 -7
  93. package/schemas/probability-compare-input.schema.json +1 -1
  94. package/schemas/probability-evaluate-input.schema.json +1 -1
  95. package/schemas/probability-inspect-input.schema.json +1 -1
  96. package/schemas/probability-render-input.schema.json +1 -1
  97. package/schemas/probability-scenario-set.schema.json +1 -1
  98. package/schemas/probability-sequence-input.schema.json +1 -1
  99. package/schemas/probability-simulate-input.schema.json +1 -1
  100. package/schemas/probability-sweep-input.schema.json +1 -1
  101. package/schemas/reference-context-input.schema.json +1 -1
  102. package/schemas/reference-read-input.schema.json +1 -1
  103. package/schemas/reference-search-input.schema.json +1 -1
  104. package/schemas/reference-sheets.schema.json +267 -0
  105. package/schemas/scenario-suite.schema.json +1 -1
  106. package/schemas/scenario-test-input.schema.json +1 -1
  107. package/schemas/script-validate-input.schema.json +1 -1
  108. package/schemas/shared-scenario.schema.json +1 -1
  109. package/schemas/source-lookup-input.schema.json +1 -1
  110. package/server.json +2 -2
package/docs/map.md CHANGED
@@ -29,24 +29,139 @@ Automatic interpretation recognizes visual land/water and terrain cues, derives
29
29
 
30
30
  Optional `reference` controls refine the same call:
31
31
 
32
- | Field | Meaning |
33
- | ----------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
- | `politicalImagePath`, `terrainImagePath`, `heightImagePath` | Additional aligned workspace images for political regions, terrain cues or grayscale height. |
35
- | `mode` | `auto`, `illustrated`, `satellite`, `sketch`, `political` or `grayscale`. |
36
- | `crop` | `{x,y,width,height}` in original source pixels. |
37
- | `ignoreMasks` | Polygons that exclude legends, frames or decorative areas. |
38
- | `annotations` | `{kind,polygon}` semantic polygons: sea, land, plains, lake, forest, desert, mountain, snow, volcano, hills, marsh or jungle. |
39
- | `riverAnnotations` | Paths for known rivers. |
40
- | `palette` | `{kind,color:{r,g,b}}` color cues for the same semantic kinds. |
41
- | `coastDetail` | Detail preservation from 0 to 1. |
42
- | `cities` | `{name,x,y,countryTag?}` reviewed city and optional capital anchors. `countryTag` must name a generated country. |
43
- | `labels` | `{name,x,y,kind}` reviewed names for `state`, `country`, `city`, `sea`, `region`, `mountain` or `geography`; useful for handwritten or ambiguous labels. |
32
+ | Field | Meaning |
33
+ | ----------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
34
+ | `politicalImagePath`, `terrainImagePath`, `heightImagePath` | Additional aligned workspace images for political regions, terrain cues or grayscale height. |
35
+ | `mode` | `auto`, `illustrated`, `satellite`, `sketch`, `political` or `grayscale`. |
36
+ | `crop` | `{x,y,width,height}` in original source pixels. |
37
+ | `ignoreMasks` | Polygons that exclude legends, frames or decorative areas. |
38
+ | `annotations` | `{kind,polygon}` semantic polygons: sea, land, plains, lake, forest, desert, mountain, snow, volcano, hills, marsh or jungle. |
39
+ | `riverAnnotations` | Paths for known rivers. |
40
+ | `palette` | `{kind,color:{r,g,b}}` color cues for the same semantic kinds. |
41
+ | `coastDetail` | Detail preservation from 0 to 1. |
42
+ | `paletteMatch` | `nearest` (default) snaps each pixel to the closest `palette` colour. `exact` requires every pixel to equal a palette colour and trusts the classes as painted (no text masking, cleanup, smoothing or political inference; thin painted water stays water); any other colour fails with `MAP_REFERENCE_PALETTE_UNKNOWN_COLOR`, listing up to 16 colours with counts and five source pixels each. |
43
+ | `fit` | `contain` (default) centres the picture with sea padding. `frame` maps the picture, or its `crop`, onto exactly `width` x `height` (both required, same shape within 1%, else `MAP_REFERENCE_FRAME_ASPECT`) and reads it at the frame resolution instead of the 3,072-pixel working copy. |
44
+ | `cities` | `{name,x,y,countryTag?}` reviewed city and optional capital anchors. `countryTag` must name a generated country. |
45
+ | `labels` | `{name,x,y,kind}` reviewed names for `state`, `country`, `city`, `sea`, `region`, `mountain` or `geography`; useful for handwritten or ambiguous labels. |
44
46
 
45
47
  Reviewed geography labels preserve names for lakes, rivers, forests and other features in localisation and generated-world provenance. They do not invent a native HOI4 map entity or turn a geographic label into a city or victory point.
46
48
 
47
49
  All annotation positions and polygons use normalized 0 to 1 coordinates relative to the original, uncropped reference, with the origin at its top left. Crop uses source pixels instead. Explicit `countries` on the operation supplies reviewed country tags, names, colors and relative shares. If borders or names cannot be inferred reliably, the server generates named defaults and reports the assumptions. Manual controls are optional and never change the original image.
48
50
 
49
- Only relative paths inside the authorized mod workspace are accepted. URLs, absolute paths, traversal, generated storage and symlink escapes are refused. The source is bounded to 1 GiB and 300 million decoded pixels; analysis uses a reduced working PNG up to 3072 pixels per side. The verified source handle is streamed into an exclusive private temporary snapshot, and the same copied bytes are hashed and decoded; the snapshot is removed on completion, failure or cancellation. The original SHA-256 is checked again before writing, using a streaming read, so replacing the source after preparation blocks application. CI image fixtures are synthetic and project-owned. Attributed comparison images may be published separately with permission and remain outside the npm package.
51
+ ### Registered drawings and import evidence
52
+
53
+ A picture made of several drawn sheets, each at its own place, scale or orientation, is registered onto the world frame with `reference.sheets` (inline) or `reference.sheetsPath` (a workspace-relative `.json` file read with the same guards as `geographyPath`). The document is [reference-sheets version 1](../schemas/reference-sheets.schema.json):
54
+
55
+ ```json
56
+ {
57
+ "version": 1,
58
+ "source": { "width": 17520, "height": 12720 },
59
+ "fill": "sea",
60
+ "sheets": [
61
+ {
62
+ "id": "cold",
63
+ "box": [0, 0, 0.395, 0.245],
64
+ "anchor": [0.043, 0.087],
65
+ "target": [0.04, 0.17],
66
+ "scale": [1.037, 1.506]
67
+ },
68
+ {
69
+ "id": "east",
70
+ "box": [0.036, 0.5, 0.4, 1],
71
+ "anchor": [0.142, 0.7],
72
+ "target": [0.89, 0.618],
73
+ "scale": [-1.13, 1.66],
74
+ "order": 1
75
+ }
76
+ ]
77
+ }
78
+ ```
79
+
80
+ Each sheet maps picture coordinates to world coordinates as `world = target + scale * (picture - anchor)` per axis (a negative scale mirrors), or by `affine: [a, b, c, d, e, f]`. `box` is the sheet's part of the picture, `clip` an optional polygon that cuts it out of its box, and `order` decides which sheet wins where targets overlap; two overlapping sheets of equal order are refused. World pixels outside every sheet are open sea (`fill: "sea"`) or an error (`"error"`). `source` pins the picture size and optionally its SHA-256, so registrations made for one picture are never applied to another. Sheets imply `fit: "frame"`; `width` and `height` are required.
81
+
82
+ The picture is read once at the resolution the sheets need (one working pixel per world pixel for the most enlarged sheet, at most 32 million pixels), each world pixel is sampled from its sheet (nearest under `paletteMatch: "exact"`, bilinear otherwise), and the registered picture is classified as one frame-aligned picture from which every map layer derives. With sheets, `annotations`, `ignoreMasks`, `riverAnnotations`, `labels` and `cities` use world coordinates, and recognised text is never used as a name. `crop` and the aligned political, terrain and height pictures cannot be combined with sheets yet.
83
+
84
+ A request with sheets, `fit: "frame"`, `paletteMatch: "exact"` or a geography model beside the picture returns import evidence: `map-import-report.json` (sheet coverage and hidden pixels, class shares and, with a model, the land agreement: intersection over union, land only in the picture or only in the model, the twelve worst 128-pixel cells, and model sites, volcanoes and river ends on the wrong surface), `map-import-classes.png`, `map-import-alignment.png` (the picture darkened, its coast in red and the model's in yellow) and, with sheets, `map-import-registration.png`. Land agreement below `reference.agreement.minLandIou` (default 0.85) is the error `MAP_IMPORT_SOURCES_DISAGREE`, which blocks the write; below `warnLandIou` (default 0.95) it is a warning. A preview with a model checks the model, the registration and the agreement without planning a map and reports the figures in `data.import`. See ADR 0067.
85
+
86
+ Only relative paths inside the authorized mod workspace are accepted. URLs, absolute paths, traversal, generated storage and symlink escapes are refused. The source is bounded to 1 GiB and 300 million decoded pixels; analysis uses a reduced working PNG up to 3072 pixels per side, or, with `fit: "frame"`, a copy at the frame resolution (never enlarged, at most 32 million pixels; nearest-neighbour reduction under `paletteMatch: "exact"`, so no new colours appear). The verified source handle is streamed into an exclusive private temporary snapshot, and the same copied bytes are hashed and decoded; the snapshot is removed on completion, failure or cancellation. The original SHA-256 is checked again before writing, using a streaming read, so replacing the source after preparation blocks application. CI image fixtures are synthetic and project-owned. Attributed comparison images may be published separately with permission and remain outside the npm package.
87
+
88
+ ## Describe a world as a geography model
89
+
90
+ A reference picture says where land and water are. A geography model says what the world is: seas, lands, mountain ranges with passes, volcanoes, rivers from a lake to a sea, lakes, forests and deserts, straits, sites and climate, each with an id and a name. `create_world` accepts one as `geography` (inline) or `geographyPath` (a workspace-relative `.json` file of at most 4 MiB; prefer the file for anything beyond a few hundred vertices). It may stand alone or accompany a `reference` picture. [geography-model.schema.json](../schemas/geography-model.schema.json) is the complete schema; [ADR 0055](adr/0055-geography-model-input.md) records the design.
91
+
92
+ Preview it before anything else:
93
+
94
+ ```json
95
+ {
96
+ "preview": true,
97
+ "operations": [
98
+ {
99
+ "kind": "create_world",
100
+ "width": 5120,
101
+ "height": 2560,
102
+ "geographyPath": "geography/world.json"
103
+ }
104
+ ]
105
+ }
106
+ ```
107
+
108
+ The preview validates the model, resolves its ids and areas, and returns `map-geography-overlay.png` (every feature drawn with its id; defects outlined in red for errors and orange for warnings) and `map-geography-report.json` (resolved ids, areas in frame units and pixels, lengths, resolved references, climate and every diagnostic), with a summary in `data.geography`. It reads only the model: no map is planned and no mod file changes. The overlay is an offline drawing, not a game screenshot.
109
+
110
+ A request without `preview` builds the world from the model: land, relief, lakes and rivers are synthesised (see [Relief and rivers from a model](#relief-and-rivers-from-a-model)). A model with errors is blocked with `MAP_GEOGRAPHY_INVALID`. Biomes, climate, fertility, region terrain shares and sea ice belong to a later stage: a model that declares them is blocked with `MAP_GEOGRAPHY_SYNTHESIS_UNAVAILABLE`, whose message names the parts, and the preview's `data.geography[].synthesis` and `unsupported` say so beforehand. Nothing is written when a request is blocked.
111
+
112
+ A model is one object with `"version": 1` and any of these arrays. Unknown keys are refused.
113
+
114
+ | Array | Feature and main fields |
115
+ | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116
+ | `seas` | `polygons` (outer rings), optional `holes`, `depthClass` (`shelf`, `open`, `abyss`), `ice` 0 to 1, `kind` (`ocean`, `sea`, `gulf`, `fjord`) |
117
+ | `lands` | `polygons`, `holes`, `kind` (`continent`, `island`, `archipelago`, `peninsula`), `region`, default `biome` and `fertility`, `elevationBase`, `coastDetail` |
118
+ | `regions` | `polygons` (optional), `terrain` target shares, `density` (`provincesPerState` as `[smallest, usual, largest]`, `provinceAreaPx`) |
119
+ | `ranges` | `path` (points, each optionally with `halfWidth` and `peak`), `closed`, `halfWidth`, `peak`, `profile` (`ridge`, `massif`, `escarpment`, `wall`), `asymmetry`, `snowline`, `spurs`, `barrier` (`none`, `impassable`, `fort-line`) |
120
+ | `passes` | `range` with `at` (0 to 1 along the range), or a `position` point (the range is found for you), `width`, `depth`, `fort` 0 to 5 |
121
+ | `volcanoes` | `at`, `radius`, `peak`, `crater`, `active` |
122
+ | `plateaus` | `polygons`, `height` (a low value makes a basin or chasm), `edge` (`cliff`, `ramp`) |
123
+ | `rivers` | `source` (point or `{lake}`), `mouth` (point, `{sea}`, `{lake}` or `{river}` for a tributary), `via` points, `size` 1 to 5, `delta` |
124
+ | `lakes` | `polygons` or `center` with `radius`, `depthClass`, `outflow` (a river id), `gameplay` (`lake`, `inland-sea`) |
125
+ | `biomes` | `kind` (`forest`, `desert`, `marsh`, `ice`, `plains`, `farmland`, `jungle`, `steppe`, `tundra`, `hills`), `polygons`, `density`, `fertility`, `z` priority |
126
+ | `straits` | `between` (two points on land or `{land}` ids), a `path` through the water, or both; `rule` (`open`, `army-closed`, `navy-closed`), `width` |
127
+ | `sites` | `kind` (`capital`, `city`, `fortress`, `port`, `ruin`, `temple`, `mine`, `landmark`), `at`, `importance` 1 to 5, `country`, `onWater`, `lake` |
128
+ | `roads` | `path`, `importance` 1 to 3 |
129
+ | `places` | `kind` (`cape`, `bay`, `gulf`, `peninsula`, `reef`, `landmark`, `other`), `at`: a name for later naming stages, on any surface |
130
+ | `climate` | `windFrom` (compass bearing the wind comes from), `temperature` (`warm` and `cold` degrees Celsius), `warmBandY`, `rainScale`, `seasonality` |
131
+
132
+ Every feature also takes `id` (letters, digits, `_`, `.`, `-`; unique across the whole model), `name`, `tags` and `note`. Coordinates are normalised to the whole world frame, origin top left, x east and y south, like `reference.labels`; a point is `{"x":0.3,"y":0.4}` or `[0.3, 0.4]`. Lengths (`radius`, `halfWidth`, `width`) are fractions of the map width, so a circle stays round at any aspect ratio. Heights (`peak`, `height`, `elevationBase`) are 0 to 1 of the relief scale. A polygon has 3 to 2,000 vertices, a path 2 to 2,000; a model has at most 4,000 features and 200,000 vertices and points. Order does not matter: features are sorted by id, rings oriented and defaults filled, and the report carries a `modelHash` that is the same for the same world however it was written.
133
+
134
+ A model that needs an edit is reported by `MAP_GEOGRAPHY_*` diagnostics that name the feature. Errors: `ID_DUPLICATE`, `RING_DEGENERATE`, `RING_SELF_INTERSECTION`, `POLYGONS_OVERLAP`, `HOLE_OUTSIDE`, `HOLES_WITHOUT_POLYGONS`, `REF_UNKNOWN`, `REF_KIND`, `PASS_SHAPE`, `PASS_OFF_RANGE`, `RIVER_SOURCE_NOT_LAND`, `RIVER_MOUTH_NOT_WATER`, `RIVER_CYCLE`, `LAKE_SHAPE`, `LAKE_NOT_IN_LAND`, `LAKE_OUT_OF_FRAME`, `VOLCANO_NOT_ON_LAND`, `VOLCANO_CRATER_RADIUS`, `STRAIT_SHAPE`, `STRAIT_ENDPOINT`, `STRAIT_SAME_LAND`, `SITE_IN_WATER`, `BIOME_CONFLICT` (different kinds overlap at the same `z`), `BIOME_OVER_WATER`, `PATH_DEGENERATE`, `CLIMATE_TEMPERATURE_ORDER`, `REGION_DENSITY_ORDER`, `NO_LAND` (no lands and no reference picture), `LIMIT_FEATURES` and `LIMIT_POINTS`. Warnings: `LAND_OVERLAP`, `SEA_OVER_LAND`, `LAKE_TOO_SMALL`, `LAKE_OUTFLOW_MISMATCH`, `PASS_OVERLAP`, `VOLCANO_OVERLAP`, `RANGE_ON_WATER`, `PATH_SELF_INTERSECTION`, `RIVER_VIA_NOT_LAND`, `STRAIT_PATH_ON_LAND`, `STRAIT_NO_WATER`, `SITE_DUPLICATE_POSITION`, `REGION_EMPTY`, `REGION_TERRAIN_SUM`, `NAME_DUPLICATE`, `RING_DUPLICATE_VERTEX`, `PATH_DUPLICATE_VERTEX`, `FEATURE_IN_EDGE_BAND` and `DIAGNOSTICS_TRUNCATED` (more than 1,000 findings; the worst severity is kept). A file that cannot be used at all fails the call instead, with `MAP_GEOGRAPHY_PATH_INVALID`, `SOURCE_TOO_LARGE`, `JSON_INVALID`, `SCHEMA_INVALID` or `SOURCE_AMBIGUOUS`. A point within 0.003 map widths of a coast counts as land, so a river mouth or a port drawn on the shore does not fail on a simplified coastline; the error for an inland mouth reports its `inlandDistance` so it can be snapped.
135
+
136
+ `FEATURE_IN_EDGE_BAND` marks land, plateaus, ranges, volcanoes, sites, passes and river points inside `max(192 pixels, 4% of the width)` east and west or 64 pixels north and south of the frame, because the game wraps the map horizontally. It is a warning, and an error when the request sets `edge.mode` to `sea-band` (`rim` and `none` skip it).
137
+
138
+ `geographyPath` follows the rules of `reference.imagePath`: relative paths inside the authorized mod workspace only; URLs, absolute paths, traversal, symlink and junction escapes are refused; the file is read once, its SHA-256 is taken from the bytes that are parsed, and a change after preparation blocks the write as stale. A file that is not JSON or does not match the schema is an error, not a partial preview. CI models are synthetic and project-owned.
139
+
140
+ ### Relief and rivers from a model
141
+
142
+ `create_world` with a geography writes `heightmap.bmp`, `world_normal.bmp`, `rivers.bmp`, the land and lake mask, and the permanent-snow terrain from the model; every other output follows the usual generator. Without a geography nothing changes. [ADR 0056](adr/0056-terrain-relief-and-hydrology-synthesis.md) records the design.
143
+
144
+ | Input | Meaning |
145
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
146
+ | `relief.scale` | `vanilla` (default), `low` (everything above the lowland 25% lower) or `dramatic` (15% higher). |
147
+ | `relief.detail` | 0 to 1, default 0.5: full-resolution roughness. |
148
+ | `rivers` | `{ "density": 0.02 }`: the share of land pixels the unnamed network covers (0 to 0.04, default 0.02; never more than 2.5% of the land or 130,000 pixels in all). An integer instead limits the number of unnamed river systems. |
149
+
150
+ What each feature becomes. Heights are heightmap values: land from 96, sea 89 (ADR 0053), a crest of `peak` stands at `100 + 148 * peak`, a plateau of `height` at `96 + 148 * height`, and the base game reaches 239 (the synthesis stops at 252).
151
+
152
+ - **Lands** take `elevationBase` for their lowland (`100 + 38 * elevationBase`, about 106 by default) and `coastDetail` for the roughness of their coast; the coast ramps from 96 over six pixels.
153
+ - **Ranges** rise along their path to their `peak` and fall to the lowland over their `halfWidth`: `ridge` has a sharp crest over a foothill apron, `massif` flat shoulders, `escarpment` a steep face on the side the `asymmetry` points to (right of the path as drawn when it is 0 or positive) and a long back slope, `wall` almost vertical sides. `asymmetry` widens one side; `spurs` (0 to 1) sets how many side ridges leave the crest; a `snowline` turns the ground above it into permanent-snow terrain; a `barrier` of `impassable` or `fort-line` marks the range in the barrier field outside its pass gaps. Crests wander up to 30% of the half-width off the drawn path.
154
+ - **Passes** cut a saddle `depth` deep and about two and a half pass widths long into the crest of their range.
155
+ - **Volcanoes** are cones from the rim of a `crater`, with radial gullies; the crater bowl is eight height units deep or more.
156
+ - **Plateaus** blend to their height inside the polygon with a short cliff or a ramp; height 0 is the land floor and, inside higher ground, a chasm.
157
+ - **Lakes** are carved out of the land and lie at 89; a lake with an `outflow` river starts that river on its lowest or nearest free shore.
158
+ - **Rivers** are routed over the land from the source (or lake shore) through the `via` points to the mouth, with bends, so a mouth that lies across a range or on another landmass is an error. Tributaries (`mouth: { river }`) join with a red pixel (index 1), a `delta` river splits into two distributaries that start in a yellow pixel (index 2), the main branch of every connected river has one green source (index 0), and widths from 3 to 11 follow the catchment and never narrow downstream. The network of unnamed rivers drains the rest of the land into them. Rivers avoid the high ground, keep a pixel apart, never form a 2 x 2 block and flow downhill in the written heights.
159
+
160
+ The synthesis checks its own output and reports: `MAP_RELIEF_SYNTHESIZED` (info: land median, 99th percentile, maximum, mean step per half-resolution pixel, river pixels and share, the first named rivers), `MAP_PASS_NOT_LOWER` (error: a pass that is not lower than the crest either side), `MAP_RIVER_UNREACHABLE_MOUTH` (error: a named river that cannot reach its mouth over land or would climb more than 22 height units), `MAP_RANGE_CREST_LOW`, `MAP_VOLCANO_NO_CRATER`, `MAP_RELIEF_FLAT`, `MAP_RIVER_STRAIGHT_RUN` (more than 3% of river pixels on straight 41-pixel stretches; the base game has 1.1%), `MAP_RIVER_DENSITY` and `MAP_RIVER_PIXEL_ROUTE_INCOMPLETE` (warnings), and `MAP_LAKE_ENDORHEIC` and `MAP_RIVER_STREAMS_DROPPED` (info). They need the model, so `hoi4.map_inspect` cannot repeat them on an existing mod; the [appearance validators](#appearance-validation) read every file written.
161
+
162
+ Measured on the project's synthetic large fantasy model at 5,120 x 2,560 (13.1 million pixels): land median 110, 99th percentile 222, maximum 244, mean height step 1.02 per half-resolution pixel (base game: 107, 213, 239 and 0.97), 6.0 million land pixels with 129,987 river pixels (2.16%, 88 connected rivers, no 2 x 2 block, one source each, 1.6% of river pixels on straight stretches), and about six seconds and 450 MB for the synthesis.
163
+
164
+ Not synthesised yet: biomes, climate, fertility, tree and colour-map classes from the model, sea ice, strait and pass adjacencies, and the render layers for relief and rivers. With a reference picture the picture still gives the coast and the terrain classes; its elevation and river hints are replaced. A reference picture's rivers are not routed.
50
165
 
51
166
  ## Inspect and navigate the complete map
52
167
 
@@ -71,8 +186,9 @@ Set `lookupOnly: true` for coordinate, query, and selected-ID lookups without fu
71
186
  | `lookupOnly` | `false` | Return only the requested lookups, as described above. |
72
187
  | `includeOverview` | `true` | Render the full-map PNG and navigator; set `false` to skip the overview render. |
73
188
  | `allocationRequests` | `[]`, at most 100 | Preview the next free identifiers: `{ "kind": "state", "requestedId"? }` or `{ "kind": "province", "requestedId"?, "requestedColor"? }`, where `requestedColor` is `{ "r", "g", "b" }`. Not accepted with `lookupOnly`. |
189
+ | `quality` | `false` | Also measure border naturalness (see [Border naturalness](#border-naturalness)). Full inspection only; not accepted with `lookupOnly` or a rendered view. |
74
190
 
75
- Give `hoi4.map_inspect` a `layer`, `overlays`, `scale`, `tile` or `area` to render that view instead of validating; the result is the same complete catalog and navigator drawn with the chosen base layer and overlays, and lookups (`query`, `coordinates`, IDs) belong in a separate call. Base layers include province, state, strategic region, terrain, continent, owner, controller, cores, claims, and coast. Overlays include coastlines, ports, victory points, resources, state and province buildings, supply nodes, railways, adjacencies, and building, unit, and weather positions.
191
+ Give `hoi4.map_inspect` a `layer`, `overlays`, `scale`, `tile` or `area` to render that view instead of validating; the result is the same complete catalog and navigator drawn with the chosen base layer and overlays, and lookups (`query`, `coordinates`, IDs) belong in a separate call. Base layers include province, state, strategic region, terrain, continent, owner, controller, cores, claims, and coast. Overlays include coastlines, ports, victory points, resources, state and province buildings, supply nodes, railways, adjacencies, building, unit, and weather positions, and `border-quality` (see [Border naturalness](#border-naturalness)).
76
192
 
77
193
  A rendered view takes `layer` (default `province`), up to 12 `overlays`, and an integer `scale` from 1 to 16 (default 1).
78
194
 
@@ -80,6 +196,42 @@ Pass `tile: { x, y, width, height }` for a reusable bounded PNG, JSON, and HTML
80
196
 
81
197
  Pass `area: { provinceIds, stateIds, regionIds, padding }` to render the exact bounding area of those map entities with optional pixel padding. The result reports the resolved tile coordinates. An unknown ID or an area larger than 2,048 pixels in either direction is explicit, so a narrower selection can be requested.
82
198
 
199
+ ## Border naturalness
200
+
201
+ A map can be structurally valid and still look artificial because its borders ignore the land. `hoi4.map_inspect` with `quality: true` measures how well province, state, strategic-region and country borders follow natural lines, and the `border-quality` overlay shows where they do not. The measurement is offline, deterministic and bounded; it reads `provinces.bmp` and the definitions, the states and regions, and (when present and the size of the province map) `heightmap.bmp`, `rivers.bmp` and `terrain.bmp` with the `common/terrain` entries. A bitmap that is absent or of another size is skipped, listed in `data.quality.skippedInputs` and reported as `MAP_BORDER_INPUT_MISSING`.
202
+
203
+ A **border** is a land-to-land pixel edge between two different groups of provinces (the east-west seam counts); the coast is counted apart. A border lies on a **natural feature** when it is within 2 pixels of a river, within 1 pixel of a steep height gradient (central difference of at least 2 per pixel), within 1 pixel of a lake, or within 1 pixel of a change between obstacle terrain classes (mountain, forest, jungle, marsh) in `terrain.bmp`. Each layer reports:
204
+
205
+ | Field | Meaning |
206
+ | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
207
+ | `alignment` | Share of border length in the barrier set (rivers, slopes, lake shores). |
208
+ | `baseRate`, `enrichment` | Share of all land on the same features, and `alignment / baseRate`. 1 is chance; vanilla is 1.35 to 1.46, the engineering target 1.6. Null (`not-meaningful`) when the base rate is under 2%. |
209
+ | `naturalShare` | The plain share of border length on any natural feature, terrain changes included; `naturalShareWithCoast` also counts the coast as a natural line. |
210
+ | `straightFraction` | Share of samples (every seventh border pixel) whose 24-pixel window of the same border is a straight line (RMS under 0.9 pixel). Vanilla country borders 0.11, target at most 0.12. |
211
+ | `longRunShare` | Share of border length in straight segments longer than 60 pixels after simplification (tolerance 3). |
212
+ | `artificialRuns` | Count and longest length of stretches that follow no feature. |
213
+ | `worstRuns` | The longest artificial runs (three inline, twenty in the artifact) with both sides, ends, length and chord, so a fix can target them. |
214
+
215
+ `MAP_BORDER_ARTIFICIAL_REVIEW` (warning) names a layer below the targets (enrichment 1.6 for every layer; straightness 0.12 for region and country, 0.25 for state, none for province because province borders are shorter than the window). `MAP_BORDER_NOT_MEANINGFUL` (info) means the map has almost no rivers or slopes to align to. `MAP_BORDER_QUALITY_UNAVAILABLE` (warning) means the map has no raster or exceeds the border-edge budget (8 million edges; a vanilla-size map has about 0.4 million). These diagnostics never fail validation. The complete report is the artifact `map-border-quality.<revision>.json`.
216
+
217
+ Measured with this implementation (the plan's script gave the enrichment values in brackets):
218
+
219
+ | Map | Layer | Enrichment | Natural share | Straight samples |
220
+ | --------------------------------------------- | -------- | ----------- | ------------- | ---------------- |
221
+ | Installed vanilla 1.19.3 (earliest bookmark) | province | 1.35 (1.37) | 0.69 | 0.67 |
222
+ | | state | 1.40 (1.42) | 0.70 | 0.20 |
223
+ | | region | 1.39 | 0.70 | 0.11 |
224
+ | | country | 1.46 (1.53) | 0.73 | 0.11 (0.093) |
225
+ | A generated mod map of 2026-10-18 | province | 0.94 (0.94) | 0.08 | 0.99 |
226
+ | | state | 0.70 (0.69) | 0.07 | 0.37 |
227
+ | | region | 0.59 | 0.06 | 0.13 |
228
+ | | country | 0.50 (0.52) | 0.09 | 0.22 (0.178) |
229
+ | Procedural `create_world`, 1024 x 512, seed 7 | state | 1.03 | 0.32 | 0.24 |
230
+
231
+ A 13.1 million pixel map takes about 1.5 seconds and 100 MB of additional memory. The mod map rows use the installed game's `common/terrain` for the terrain classes. The last row is the repository's own procedural output: its borders sit at chance level against rivers and slopes, which is what the metric exists to show. The same functions are available to other code: `measureBorderNaturalness` for a full report, `buildBorderEdgeTable` and `scoreBorderAssignment` to score many candidate groupings of provinces without reading the raster again, and `checkBorderTargets` for an acceptance check such as a rule that at least 85% of state-border length and 70% of country-border length follows nature (vanilla itself reaches 0.70 and 0.73 with the default feature radii, so fix the radii in the check).
232
+
233
+ The `border-quality` overlay is added to any base layer (state is the natural choice): state borders are green where they follow a feature and orange where they do not, and the worst artificial run of each layer is drawn two pixels wide (province white, state red, region yellow, country magenta). It works with `tile` and `area`, and scans the river and terrain bitmaps for that render only.
234
+
83
235
  ## Catalog consistency
84
236
 
85
237
  Validation reads the active `common/terrain/*.txt` categories and `map/continent.txt` (or the continent file `default.map` names) along with the map files.
@@ -273,12 +425,103 @@ Keys that take exactly one value (`capital`, `state`, `to_state`, `start_state`,
273
425
  }
274
426
  ```
275
427
 
276
- Width and height are multiples of 256 with at most 13,238,272 pixels. Land comes from noise around `continents` centres to the requested `landCoverage`, or from `landPolygons` in pixel coordinates; the map's east-west seam and polar rows stay sea. Provinces grow evenly from spread seeds (`landProvincePixels`, default 400, and `seaProvincePixels`, default 2,500, both capped so no province exceeds an eighth of the map), stay connected, avoid four-province corners and are at least 20 pixels. Enclosed water becomes land or, when large, a lake.
428
+ Width and height are multiples of 256 with at most 13,238,272 pixels. Land comes from noise around `continents` centres to the requested `landCoverage`, or from `landPolygons` in pixel coordinates; the map's east-west seam and polar rows stay sea in generated worlds, while a reference picture keeps its own edges unless you set `edge` (see [Water, colour and the world edge](#water-colour-and-the-world-edge)). Provinces grow evenly from spread seeds (or follow natural lines, see [Feature-aware provinces, states and strategic regions](#feature-aware-provinces-states-and-strategic-regions)) (`landProvincePixels`, default 400, and `seaProvincePixels`, default 2,500, both capped so no province exceeds an eighth of the map), stay connected, avoid four-province corners and are at least 20 pixels. Lakes are cut at the sea size. Extra provinces in high and wet land redistribute the requested size rather than add to it, so the land holds about its area divided by `landProvincePixels`. Before partitioning, the province count is estimated from the land and water areas: when neither size is given and the estimate exceeds 14,000, both sizes grow so the world holds about 13,000 provinces, the base game's count (`MAP_WORLD_PROVINCE_SIZE_RAISED`); sizes you choose that give more than 21,000 are refused (`MAP_PARTITION_PROVINCE_COUNT_HIGH`), because the generated text tables stop loading beyond that. A coast that crosses the east-west seam is moved by one pixel at the seam, where provinces cannot join (`MAP_WORLD_SEAM_CORNER_REPAIRED`). Enclosed water becomes land or, when large, a lake.
277
429
 
278
430
  The operation writes `provinces.bmp`, `definition.csv`, `default.map`, `continent.txt`, `heightmap.bmp`, `terrain.bmp`, `rivers.bmp` (rivers run downhill to the coast), `trees.bmp`, `cities.bmp`, `world_normal.bmp`, the land and water colour maps, `adjacencies.csv`, strategic regions (`landProvincesPerRegion`, `seaProvincesPerRegion`) with weather by latitude, states (`provincesPerState`) with population, category, buildings, resources, victory points and ports, country tags, country files, history and three-band flags, a bookmark, localisation, and every position, supply node and railway. Countries grow from spread-out capitals in proportion to `share`; each state lies within one country and one strategic region.
279
431
 
280
432
  With `replaceBaseGame` (default `true`) it adds `replace_path` lines for `history/states`, `history/countries`, `history/units`, `map/strategicregions`, `map/supplyareas` and `common/bookmarks` to `descriptor.mod`, and its info diagnostic reminds you that the launcher's `.mod` file beside the mod folder needs the same lines. Base-game events, focuses and decisions still name base-game states; `map_inspect` with `references: true` lists them for replacement or remapping. The same seed always gives the same world. Refine it with the other operations.
281
433
 
434
+ ### Feature-aware provinces, states and strategic regions
435
+
436
+ By default provinces grow evenly from spread seeds and states and regions grow by breadth-first clusters, which ignore rivers, ridges and shores. Set `partition: "feature-aware"` (default `legacy`; a world given a `geography` defaults to it) to cut along natural lines instead (ADR 0058):
437
+
438
+ - Province borders are pulled onto rivers, steep slopes and ridges, wall ranges and lake shores (a watershed whose step cost rises on them). A province that still straddles a river or wall is cut along it.
439
+ - Province size follows the land: small in fertile valleys, on coasts and beside rivers, large on mountains and wasteland (`landArea.fertile`, `hills`, `mountain`, `waste`, in pixels), and per geography region through its `density.provinceAreaPx`. Without bands they follow `landProvincePixels` (0.35, 1, 2.2 and 3.4 times, scaled so the mean stays `landProvincePixels`). Sea provinces are small near a coast and large on the open sea (`seaArea.coastal` and `open`, from `seaProvincePixels`), and a channel no wider than `narrowStraitPx` (24) gets two sea provinces across it.
440
+ - States and strategic regions are clusters over the province graph that avoid joining across rivers and ranges, prefer like ground, never mix geography regions or named seas, and respect `provincesPerState` (`[smallest, largest]`, or a region's own `density.provincesPerState`) and `regionSize`.
441
+ - `barrierStrength` (0 to 60, default 20) sets how hard borders are pulled onto natural lines; `minProvincePixels` (default 40) is the smallest planned province.
442
+
443
+ ```json
444
+ {
445
+ "id": "world",
446
+ "kind": "create_world",
447
+ "width": 5120,
448
+ "height": 2560,
449
+ "partition": "feature-aware",
450
+ "landProvincePixels": 400,
451
+ "partitionTuning": {
452
+ "landArea": { "fertile": 120, "hills": 400, "mountain": 800, "waste": 1100 },
453
+ "provincesPerState": [6, 12],
454
+ "barrierStrength": 24
455
+ }
456
+ }
457
+ ```
458
+
459
+ All the engine rules hold as before: provinces are connected, no box exceeds an eighth of the map, no four provinces meet at a corner, provinces are at least 20 pixels where a same-kind neighbour exists, and at most 65,000 exist. A plan that needs more than 21,000 provinces is refused with `MAP_PARTITION_PROVINCE_COUNT_HIGH` and the fields to change; both partitions warn with that code above 14,000 provinces. Contradictory bands (`MAP_PARTITION_AREA_BANDS_CONTRADICTORY`), reversed ranges, a map too small for `minProvincePixels` (`MAP_PARTITION_MAP_TOO_SMALL`) and `partitionTuning` with `legacy` are refused before anything is written. Info notes report the provinces cut along rivers or straits (`MAP_PARTITION_SPLITS`) and coast pixels adjusted because a 2 x 2 block touched its own kind only at a corner (`MAP_PARTITION_MASK_SMOOTHED`). Check the result with `hoi4.map_inspect` and `quality: true` (border naturalness).
460
+
461
+ Code that needs the same cut calls `partitionWorld` (`src/hoi4_agent_tools/map/partition.ts`): it takes `PartitionFields` (distance to river, ridge strength, slope, terrain class, region id, fertility and a barrier mask; `partitionFieldsFromBitmaps` computes a basic version from `heightmap.bmp`, `terrain.bmp` and `rivers.bmp`, `partitionFieldsFromGeography` adds regions and walls) and returns the provinces, the barrier raster, the size distribution and a weighted province graph (`buildProvinceGraph`, `provinceEdgeCosts`) that the territory assignment service reuses.
462
+
463
+ Measured on project-owned synthetic fixtures: a 1,024 x 512 world with a dense river net goes from province and state border enrichment 1.02 and 0.98 to 1.34 and 2.22 (natural share of state borders from 0.24 to 0.55). On a 5,120 x 2,560 fixture (400 and 2,500 pixel provinces) the legacy partition has 25,514 provinces and enrichment 0.97 and 0.90, the feature-aware one 17,061 (land mean 399 pixels, p10 228, p90 507; sea mean 2,345) and enrichment 1.75 and 3.38; it partitions in about 9 seconds and 0.8 GiB, and the whole `create_world` takes about 20 seconds. Province borders cannot be more enriched than the length of the natural lines allows; the numbers are in ADR 0058.
464
+
465
+ ## Water, colour and the world edge
466
+
467
+ Three behaviours shape how a generated map looks in the game. The tool computes them offline from the engine's own formulas; it never runs the game, so the first in-game look still needs your review.
468
+
469
+ ### Lakes
470
+
471
+ The engine draws water everywhere with an opacity that depends on the heightmap: a pixel at height 91 or lower is fully opaque, 93 is 56% opaque and 95 or higher is invisible. Every `create_world` therefore writes lake interiors at the sea height 89, with a one to two pixel bank toward the shore (93, then 91), and gives lake pixels the graphical terrain of the ocean (index 15 in the base game). A graphical terrain entry with `texture = 255`, such as the base game's `lakes` entry, names no atlas tile and is never chosen for a category.
472
+
473
+ Water that does not read as a lake is not written as one. Enclosed water below 60 pixels becomes land (`MAP_LAKE_TOO_SMALL`), and lake water that touches the open sea becomes sea (`MAP_LAKE_CONNECTED_TO_SEA`). Both appear as warnings with the pixel count and a location, so a reference picture's pond is never silently lost. The water colour maps use a shelf-to-deep gradient fitted to the base game and a lighter lake tint. This changes the output of every request that has lakes; the old output was a defect (see the changelog).
474
+
475
+ ### Colour map and graphical terrain
476
+
477
+ `colormap.profile` is `legacy` (the original flat tints, kept so existing requests do not change) or `calibrated`. `look` (`vanilla-temperate`, `fertile`, `arid` or `custom`) chooses a palette set and selects the calibrated profile; `custom` needs `colormap.palette` with `{ "plains": { "r", "g", "b" }, ... }` for any of plains, farmland, forest, hills, desert, mountain, marsh, jungle, snow, urban, lakes and ocean. The calibrated map follows the base game's measurements (land mean value about 74, over a thousand distinct colours): dark class colours varied by low-frequency noise, hillshade, wetness and a slow hue drift, with permanent snow tinted as snow instead of plains. It also writes farmland (index 5) on wet flat plains, sparse forest (4) in thinned forest and grassy mountain (20) on wet mountains, when the active terrain files define those indices.
478
+
479
+ ```json
480
+ {
481
+ "id": "world",
482
+ "kind": "create_world",
483
+ "reference": { "imagePath": "references/world.png" },
484
+ "look": "fertile",
485
+ "edge": {},
486
+ "ambientFrame": true
487
+ }
488
+ ```
489
+
490
+ ### The world edge
491
+
492
+ The engine wraps the map horizontally, so land that touches the first or last column is cut by a straight line where the world repeats. Without `edge` nothing changes and validation reports `MAP_EDGE_LAND_CUT` for such rows. With `edge`:
493
+
494
+ | `edge` field | Default | Meaning |
495
+ | ------------ | ----------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
496
+ | `mode` | `sea-band` when `edge` is given | `sea-band` clears land from the east and west edges and the poles to open sea with a ragged outline; `rim` ends land at the seam in an impassable snow range; `none` opts out. |
497
+ | `bandPx` | `max(192, 4% of the width)`, at most a quarter of the width | East and west sea band. |
498
+ | `polarPx` | 64, at most a quarter of the height | North and south sea band. |
499
+ | `rimPx` | 160, at most a quarter of the width | Width of the rim range for `rim`. Heights rise from 150 to 230, the colour map darkens and `adjacencies.csv` gets one `impassable` row per pair of land provinces across the seam. |
500
+
501
+ A band wider than a quarter of the world is refused (`MAP_EDGE_BAND_TOO_WIDE`). A city inside a band that would be cleared is an error (`MAP_EDGE_FEATURE_IN_BAND`) naming it; land that is cleared is reported with its pixel count (`MAP_EDGE_LAND_CLEARED`). `ambientFrame: true` writes the north and south frame borders and the logo frame into `ambient_object.txt` at the base map's positions scaled to the world height (the north frame at `z = height + 142`, the south frame at `z = -140`); by default the file is a comment, which drops the frame. The wrap cannot be removed with data; the band makes it invisible and the rim makes it a wall. Which looks better in the game is for you to judge with a probe map. Rivers are routed before the rim is raised, so keep them out of a rim.
502
+
503
+ ### Appearance validation
504
+
505
+ `hoi4.map_inspect` and every `create_world` check the files that decide the look, for the files the mod ships: `heightmap.bmp`, `terrain.bmp`, `rivers.bmp`, `trees.bmp`, `cities.bmp`, `world_normal.bmp` and the colour maps. Format and size defects are errors; statistical deviations are warnings. Severities are set so the installed base game raises no error.
506
+
507
+ | Code | Severity | Meaning |
508
+ | -------------------------------------------------------------------------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
509
+ | `MAP_LAKE_SURFACE_TOO_HIGH` | error or warning | Lake pixels above height 91 away from the shore; an error when more than 5% of the lake. |
510
+ | `MAP_LAKE_TEXTURE_UNRENDERED` | error | A pixel uses a graphical terrain with `texture = 255`. |
511
+ | `MAP_LAKE_TOO_SMALL`, `MAP_LAKE_CONNECTED_TO_SEA`, `MAP_HEIGHT_LAKE_ABOVE_SURFACE` | warning | A lake body under 60 pixels, a lake province bordering the sea, lake pixels at or above the water surface. |
512
+ | `MAP_HEIGHT_FORMAT`, `_SIZE`, `_LAND_BELOW_COAST`, `_SEA_ABOVE_SURFACE`, `_SEAM_STEP` | error or warning | Heightmap depth and size, land under 94, sea above 94 and a step between the first and last column. |
513
+ | `MAP_TERRAIN_BITMAP_FORMAT`, `_SIZE`, `_INDEX_UNDEFINED` | error or warning | Terrain bitmap depth, size and palette indices no `common/terrain` entry defines (an error when widespread). |
514
+ | `MAP_RIVER_FORMAT`, `_SIZE`, `_INDEX_INVALID`, `_BLOCK` | error | River bitmap depth, size, indices other than 0 to 11, 254 and 255, and 2 x 2 blocks of river pixels (a warning when rare). |
515
+ | `MAP_RIVER_PALETTE_COUNT`, `_NOT_ORTHOGONAL`, `_SOURCE_COUNT`, `_ON_WATER`, `_DENSITY` | warning | Header colour count other than 0, pixels with no orthogonal river neighbour, rivers without exactly one source, river pixels on sea, more than 2.5% of land or 130,000 pixels. |
516
+ | `MAP_TREES_FORMAT`, `_SIZE`, `_INDEX_INVALID`, `_ON_WATER` | error or warning | Tree bitmap depth, size (75/256 of the map), indices beyond the palette and trees over open water. |
517
+ | `MAP_COLORMAP_SIZE` | error | A colour map that is not half (land) or 1/2, 1/4, 1/8 (water) of the province map. |
518
+ | `MAP_COLORMAP_RANGE`, `MAP_WATER_COLORMAP_FLAT` | warning | Land mean value outside 50 to 100, fewer than 400 distinct 5-bit colours or a deviation under 15; a flat water map. |
519
+ | `MAP_NORMAL_SIZE`, `_NOT_UNIT`, `_RESPONSE`, `MAP_CITIES_SIZE`, `_INDEX_UNDEFINED` | error or warning | Normal map size and unit length, its response to the height step (the base game's is about 2.6) and the city map. |
520
+ | `MAP_EDGE_LAND_CUT` | warning or error | Land on the first or last column; an error when the world was created with `sea-band`. |
521
+ | `MAP_SEAM_LAND_ADJACENCY`, `MAP_POLAR_LAND`, `MAP_COLORMAP_ALPHA_CITY` | info | Land provinces that meet across the seam (and how many are blocked), land in the top and bottom rows, city lights over water. |
522
+
523
+ Not implemented: a check that river widths never narrow downstream (it needs a flow direction the file does not hold), and a mesh check for `trees.bmp` beyond the declared palette. Bitmaps are read in row strips and validation yields between strips, so a 13-million-pixel map stays cancellable and within memory.
524
+
282
525
  ## Worked example: build a world, then reshape it
283
526
 
284
527
  1. Create the world. The same seed always gives the same world.
package/docs/tools.md CHANGED
@@ -43,10 +43,10 @@ MCP clients may ask for approval to create review files.
43
43
 
44
44
  ## Maps
45
45
 
46
- | Tool | Purpose | Writes |
47
- | ------------------ | -------------------------------------------------------------------------------------------------------------------------------------------- | ------------ |
48
- | `hoi4.map_inspect` | Find map problems, search and look up places; render a layer, overlays, tile or area; check script references to map entities. | review files |
49
- | `hoi4.map_rewrite` | Create a new world, or create, split, merge and renumber states and provinces, regenerate positions and supply, and remap script references. | mod files |
46
+ | Tool | Purpose | Writes |
47
+ | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------ |
48
+ | `hoi4.map_inspect` | Find map problems, search and look up places; render a layer, overlays, tile or area; check script references to map entities; measure how well borders follow rivers, slopes and terrain. | review files |
49
+ | `hoi4.map_rewrite` | Create a new world, or create, split, merge and renumber states and provinces, regenerate positions and supply, and remap script references. | mod files |
50
50
 
51
51
  ## Events
52
52
 
@@ -5,17 +5,17 @@ This page lists the codes you are most likely to meet, then the checks for probl
5
5
 
6
6
  ## Setup and connection
7
7
 
8
- | Symptom | Cause and fix |
9
- | ----------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
- | The client lists no `hoi4.*` tools | The server did not start. Run `hoi4-agent-tools-setup --diagnose`, which checks the Node.js version (22.19 or newer, or 24), the image library and the configured paths. Then restart the client. |
11
- | `WORKSPACE_CONTEXT_NOT_FOUND` | The server does not know which mod you mean. Open the mod folder as the client's working directory, or configure it with `--mod-root` or a workspace in the [configuration file](configuration.md). |
12
- | `WORKSPACE_CONTEXT_AMBIGUOUS` | The working directory is inside more than one configured mod. Pass `workspaceId`, or open the one folder you mean. |
13
- | `WORKSPACE_NOT_REGISTERED` | The `workspaceId` is not in the configuration. Use `"current"`, or a workspace id from the configuration file. |
14
- | Renders lack icons or fonts, native commands are unresolved | The game folder is not configured. Set `--game-root` (or `gameRoot`) to the Hearts of Iron IV folder; `hoi4-agent-tools-setup --discover` prints the folders it can find. |
15
- | `ERROR_LOG_NOT_FOUND` | Launch the game once, or set `gameUserRoot` to the folder that holds `logs/`, `mod/` and `save games/` (with OneDrive that is often under `OneDrive\Documents`). |
16
- | `AUTH_INVALID` over HTTP | The bearer token is missing, expired or for another server. See the [HTTP guide](http.md). |
17
- | Startup or a call times out in the client | A cold scan of a large mod takes minutes. Raise the client's startup and tool timeouts (the printed Codex entries use 120 and 600 seconds), or use [background jobs](jobs.md). |
18
- | Many idle `node` processes | Some clients keep a server per subagent. Set `HOI4_AGENT_STDIO_IDLE_EXIT_MINUTES`; see [Server lifetime](setup.md#server-lifetime). |
8
+ | Symptom | Cause and fix |
9
+ | ----------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
10
+ | The client lists no `hoi4.*` tools | The server did not start. Run `hoi4-agent-tools-setup --diagnose`, which checks the Node.js version (22.19 or newer, or 24), the image library and the configured paths. Then restart the client. |
11
+ | `WORKSPACE_CONTEXT_NOT_FOUND` | The server does not know which mod you mean. Pass `workspaceId` (an ID or the mod folder name, such as `"my_mod"`; the error lists them), open the mod folder as the client's working directory, set `defaultWorkspace`, or configure the mod with `--mod-root` or a workspace in the [configuration file](configuration.md). |
12
+ | `WORKSPACE_CONTEXT_AMBIGUOUS` | The working directory is inside more than one configured mod. Pass `workspaceId`, or open the one folder you mean. |
13
+ | `WORKSPACE_NOT_REGISTERED` | The `workspaceId` is not in the configuration. Use `"current"`, or a workspace id from the configuration file. |
14
+ | Renders lack icons or fonts, native commands are unresolved | The game folder is not configured. Set `--game-root` (or `gameRoot`) to the Hearts of Iron IV folder; `hoi4-agent-tools-setup --discover` prints the folders it can find. |
15
+ | `ERROR_LOG_NOT_FOUND` | Launch the game once, or set `gameUserRoot` to the folder that holds `logs/`, `mod/` and `save games/` (with OneDrive that is often under `OneDrive\Documents`). |
16
+ | `AUTH_INVALID` over HTTP | The bearer token is missing, expired or for another server. See the [HTTP guide](http.md). |
17
+ | Startup or a call times out in the client | A cold scan of a large mod takes minutes. Raise the client's startup and tool timeouts (the printed Codex entries use 120 and 600 seconds), or use [background jobs](jobs.md). |
18
+ | Many idle `node` processes | Some clients keep a server per subagent. Set `HOI4_AGENT_STDIO_IDLE_EXIT_MINUTES`; see [Server lifetime](setup.md#server-lifetime). |
19
19
 
20
20
  ## Arguments and results
21
21