hoi4-agent-tools 4.5.0 → 4.6.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 (76) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/hoi4_agent_tools/map/border-quality.js +25 -7
  3. package/dist/hoi4_agent_tools/map/derived.js +16 -3
  4. package/dist/hoi4_agent_tools/map/gameplay/crossing-validation.js +268 -0
  5. package/dist/hoi4_agent_tools/map/gameplay/crossings.js +961 -0
  6. package/dist/hoi4_agent_tools/map/gameplay/development.js +168 -0
  7. package/dist/hoi4_agent_tools/map/gameplay/economy.js +75 -0
  8. package/dist/hoi4_agent_tools/map/gameplay/facts.js +264 -0
  9. package/dist/hoi4_agent_tools/map/gameplay/inland-sea.js +66 -0
  10. package/dist/hoi4_agent_tools/map/gameplay/metrics.js +132 -0
  11. package/dist/hoi4_agent_tools/map/gameplay/names.js +419 -0
  12. package/dist/hoi4_agent_tools/map/gameplay/ports.js +244 -0
  13. package/dist/hoi4_agent_tools/map/gameplay/prepare.js +27 -0
  14. package/dist/hoi4_agent_tools/map/gameplay/rail-cost.js +77 -0
  15. package/dist/hoi4_agent_tools/map/gameplay/sites.js +61 -0
  16. package/dist/hoi4_agent_tools/map/geography/resolve.js +15 -4
  17. package/dist/hoi4_agent_tools/map/operations.js +109 -32
  18. package/dist/hoi4_agent_tools/map/relief/hydrology.js +316 -192
  19. package/dist/hoi4_agent_tools/map/relief/macro-relief.js +107 -0
  20. package/dist/hoi4_agent_tools/map/relief/terrain-synthesis.js +52 -0
  21. package/dist/hoi4_agent_tools/map/service.js +78 -16
  22. package/dist/hoi4_agent_tools/map/state-restructure.js +378 -0
  23. package/dist/hoi4_agent_tools/map/territory-graph.js +238 -0
  24. package/dist/hoi4_agent_tools/map/territory-operations.js +944 -0
  25. package/dist/hoi4_agent_tools/map/territory-quality.js +189 -0
  26. package/dist/hoi4_agent_tools/map/territory.js +1108 -0
  27. package/dist/hoi4_agent_tools/map/tool-operations.js +78 -0
  28. package/dist/hoi4_agent_tools/map/validation.js +4 -0
  29. package/dist/hoi4_agent_tools/map/world.js +301 -107
  30. package/dist/hoi4_agent_tools/reference/known-mistakes.js +3 -0
  31. package/dist/hoi4_agent_tools/schemas/geography.js +12 -1
  32. package/dist/hoi4_agent_tools/schemas/map.js +206 -21
  33. package/dist/hoi4_agent_tools/version.js +1 -1
  34. package/docs/map.md +100 -4
  35. package/npm-shrinkwrap.json +2 -2
  36. package/package.json +1 -1
  37. package/schemas/configuration.schema.json +1 -1
  38. package/schemas/continuous-focus-palette.schema.json +1 -1
  39. package/schemas/custom-weighted-pool.schema.json +1 -1
  40. package/schemas/decision-inspect-input.schema.json +1 -1
  41. package/schemas/error-log-input.schema.json +1 -1
  42. package/schemas/event-feature-manifest.schema.json +1 -1
  43. package/schemas/focus-plan.schema.json +1 -1
  44. package/schemas/focus-planning-sidecar.schema.json +1 -1
  45. package/schemas/geography-model.schema.json +13 -1
  46. package/schemas/gui-animation-source.schema.json +1 -1
  47. package/schemas/gui-helper.schema.json +1 -1
  48. package/schemas/gui-scenario.schema.json +1 -1
  49. package/schemas/helper-expansion-request.schema.json +1 -1
  50. package/schemas/helper-expansion-summary.schema.json +1 -1
  51. package/schemas/impact-inspect-input.schema.json +1 -1
  52. package/schemas/map-operation.schema.json +1355 -736
  53. package/schemas/mechanic-test-input.schema.json +1 -1
  54. package/schemas/mod-index-input.schema.json +1 -1
  55. package/schemas/operation-result.schema.json +1 -1
  56. package/schemas/package-check-input.schema.json +1 -1
  57. package/schemas/package-manifest.schema.json +1 -1
  58. package/schemas/probability-analysis-result.schema.json +1 -1
  59. package/schemas/probability-compare-input.schema.json +1 -1
  60. package/schemas/probability-evaluate-input.schema.json +1 -1
  61. package/schemas/probability-inspect-input.schema.json +1 -1
  62. package/schemas/probability-render-input.schema.json +1 -1
  63. package/schemas/probability-scenario-set.schema.json +1 -1
  64. package/schemas/probability-sequence-input.schema.json +1 -1
  65. package/schemas/probability-simulate-input.schema.json +1 -1
  66. package/schemas/probability-sweep-input.schema.json +1 -1
  67. package/schemas/reference-context-input.schema.json +1 -1
  68. package/schemas/reference-read-input.schema.json +1 -1
  69. package/schemas/reference-search-input.schema.json +1 -1
  70. package/schemas/reference-sheets.schema.json +1 -1
  71. package/schemas/scenario-suite.schema.json +1 -1
  72. package/schemas/scenario-test-input.schema.json +1 -1
  73. package/schemas/script-validate-input.schema.json +1 -1
  74. package/schemas/shared-scenario.schema.json +1 -1
  75. package/schemas/source-lookup-input.schema.json +1 -1
  76. package/server.json +2 -2
@@ -79,6 +79,15 @@ export const geographySiteKindSchema = z.enum([
79
79
  'mine',
80
80
  'landmark',
81
81
  ]);
82
+ /** The strategic resources of the game that a mine site can yield. */
83
+ export const geographyResourceSchema = z.enum([
84
+ 'steel',
85
+ 'aluminium',
86
+ 'tungsten',
87
+ 'chromium',
88
+ 'oil',
89
+ 'rubber',
90
+ ]);
82
91
  export const geographyPlaceKindSchema = z.enum([
83
92
  'cape',
84
93
  'bay',
@@ -94,7 +103,7 @@ const seaSchema = z
94
103
  ...areaFields,
95
104
  depthClass: geographyDepthClassSchema.optional(),
96
105
  ice: unit.optional(),
97
- kind: z.enum(['ocean', 'sea', 'gulf', 'fjord']).optional(),
106
+ kind: z.enum(['ocean', 'sea', 'gulf', 'bay', 'fjord']).optional(),
98
107
  })
99
108
  .strict();
100
109
  const landSchema = z
@@ -266,6 +275,8 @@ const siteSchema = z
266
275
  onWater: z.boolean().optional(),
267
276
  /** A lake the site belongs to, for a port on a lake shore. */
268
277
  lake: geographyFeatureIdSchema.optional(),
278
+ /** What a `mine` site yields in the world's economy (steel when it names none). */
279
+ resource: geographyResourceSchema.optional(),
269
280
  })
270
281
  .strict();
271
282
  const roadSchema = z
@@ -278,6 +278,32 @@ export const worldColormapSchema = z
278
278
  export const worldPartitionSchema = z
279
279
  .enum(['legacy', 'feature-aware'])
280
280
  .describe('legacy (default) keeps the warped Voronoi provinces and breadth-first clusters, so a seed gives the same world as before; feature-aware cuts provinces along rivers, ridges, steep slopes and shores, sizes them by terrain, fertility and coast, and clusters states and regions along natural lines. A world given a geography defaults to feature-aware.');
281
+ /** Development model of a world built from a geography (ADR 0063). */
282
+ export const worldDevelopmentSchema = z
283
+ .object({
284
+ model: z
285
+ .enum(['geography', 'legacy'])
286
+ .optional()
287
+ .describe('geography (default for a geography world) plans ports, population, state categories, infrastructure, factories, resources, victory points, names and railway costs from the model: fertility, rivers, the coast, passes, ranges and sites. legacy keeps the random economy.'),
288
+ })
289
+ .strict();
290
+ /** Automatic detection of narrow sea channels between landmasses (ADR 0062). */
291
+ export const worldAutoStraitsSchema = z
292
+ .union([
293
+ z.boolean(),
294
+ z
295
+ .object({
296
+ maxGapPx: z
297
+ .number()
298
+ .int()
299
+ .min(4)
300
+ .max(96)
301
+ .optional()
302
+ .describe('Widest channel written as a crossing, in pixels; default 24 (about 170 km).'),
303
+ })
304
+ .strict(),
305
+ ])
306
+ .describe('Also write a sea crossing for every channel narrower than maxGapPx that one sea province fills between two landmasses and that the model does not declare as a strait. Needs a geography.');
281
307
  const partitionAreaPixels = z.number().int().min(20).max(50_000);
282
308
  export const worldPartitionTuningSchema = z
283
309
  .object({
@@ -634,6 +660,162 @@ const createProvinceOperation = z
634
660
  })
635
661
  .strict();
636
662
  const occurrenceSchema = z.number().int().min(0).optional();
663
+ const stateChangesSchema = z
664
+ .object({
665
+ capital: provinceId
666
+ .nullable()
667
+ .optional()
668
+ .describe('Assertion for the capital derived from the exact victoryPoints payload; never writes a state capital field'),
669
+ manpower: z.number().min(0).optional(),
670
+ category: z.string().min(1).optional(),
671
+ resources: nonnegativeMap.optional(),
672
+ stateBuildings: nonnegativeMap.optional(),
673
+ owner: z.string().min(1).nullable().optional(),
674
+ controller: z.string().min(1).nullable().optional(),
675
+ cores: z.array(z.string().min(1)).max(10_000).optional(),
676
+ claims: z.array(z.string().min(1)).max(10_000).optional(),
677
+ victoryPoints: z
678
+ .array(z.object({ provinceId, value: z.number().min(0) }).strict())
679
+ .max(100_000)
680
+ .optional(),
681
+ provinceBuildings: z.record(z.string().regex(/^\d+$/u), nonnegativeMap).optional(),
682
+ })
683
+ .strict();
684
+ const tagSchema = z
685
+ .string()
686
+ .regex(/^[A-Z0-9]{3}$/u, 'A country tag is three capital letters or digits');
687
+ const territoryRefSchema = z.union([
688
+ z.object({ state: stateId }).strict(),
689
+ z.object({ province: provinceId }).strict(),
690
+ z.object({ x: z.number().int().min(0), y: z.number().int().min(0) }).strict(),
691
+ ]);
692
+ const territorySelectionSchema = z
693
+ .object({
694
+ stateIds: z.array(stateId).max(100_000).optional(),
695
+ provinceIds: z.array(provinceId).max(100_000).optional(),
696
+ regionIds: z.array(z.number().int().positive()).max(10_000).optional(),
697
+ })
698
+ .strict();
699
+ const borderQualityModeSchema = z
700
+ .enum(['off', 'report', 'enforce'])
701
+ .describe('off skips the measurement; report adds border naturalness to the result; enforce blocks the write when the borders miss the targets.');
702
+ const borderTargetsSchema = z
703
+ .object({
704
+ minEnrichment: z.number().min(0).max(20).optional(),
705
+ maxStraightness: z.number().min(0).max(1).optional(),
706
+ minNaturalShare: z.number().min(0).max(1).optional(),
707
+ })
708
+ .strict()
709
+ .describe('Override the targets (enrichment 1.6; straightness 0.12 for country borders and 0.25 for state borders).');
710
+ const territoryCountrySchema = z
711
+ .object({
712
+ tag: tagSchema,
713
+ weight: z
714
+ .number()
715
+ .positive()
716
+ .max(1_000_000)
717
+ .optional()
718
+ .describe('Relative target size; default 1.'),
719
+ seeds: z
720
+ .array(territoryRefSchema)
721
+ .min(1)
722
+ .max(64)
723
+ .describe('{ state }, { province } or a pixel { x, y }. The first is the capital.'),
724
+ forced: territorySelectionSchema.optional(),
725
+ blocked: territorySelectionSchema.optional(),
726
+ regions: z
727
+ .array(z.number().int().positive())
728
+ .max(1_000)
729
+ .optional()
730
+ .describe('Strategic regions this country prefers to grow in.'),
731
+ overlord: tagSchema.optional().describe('Grow this country inside its overlord.'),
732
+ mergeInto: tagSchema
733
+ .optional()
734
+ .describe('Relabel the finished territory to another listed country (this tag stays a core).'),
735
+ cores: territorySelectionSchema.optional(),
736
+ claims: territorySelectionSchema.optional(),
737
+ })
738
+ .strict();
739
+ const territoryOperation = z
740
+ .object({
741
+ ...operationBase,
742
+ kind: z.literal('assign_territory'),
743
+ unit: z
744
+ .enum(['state', 'province'])
745
+ .default('state')
746
+ .describe('state assigns whole states. province assigns provinces and regroups the states a border cuts (conserving population, resources and buildings).'),
747
+ countries: z.array(territoryCountrySchema).min(1).max(512),
748
+ scope: z
749
+ .object({
750
+ stateIds: z.array(stateId).max(100_000).optional(),
751
+ regionIds: z.array(z.number().int().positive()).max(10_000).optional(),
752
+ owners: z.array(z.string().min(1).max(32)).max(1_000).optional(),
753
+ })
754
+ .strict()
755
+ .optional()
756
+ .describe('Only these states take part; the rest keep their owners.'),
757
+ unowned: territorySelectionSchema.optional(),
758
+ barriers: z
759
+ .enum(['natural', 'none'])
760
+ .default('natural')
761
+ .describe('none ignores rivers, ranges and shores (nearest seed).'),
762
+ rules: z
763
+ .object({
764
+ connectedMainland: z.boolean().default(true),
765
+ islandPolicy: z.enum(['one-owner', 'nearest', 'unowned']).default('one-owner'),
766
+ })
767
+ .strict()
768
+ .optional(),
769
+ smoothing: z
770
+ .object({
771
+ passes: z.number().int().min(0).max(20).default(4),
772
+ maxShareChange: z.number().min(0).max(1).default(0.15),
773
+ })
774
+ .strict()
775
+ .optional(),
776
+ shareTolerance: z.number().min(0.01).max(1).default(0.15),
777
+ seed: z.number().int().min(0).max(2_147_483_647).default(0),
778
+ ownership: z
779
+ .object({
780
+ controller: z.enum(['owner', 'keep']).default('owner'),
781
+ cores: z.enum(['keep', 'owner', 'add-owner']).default('keep'),
782
+ claims: z.enum(['keep', 'clear']).default('keep'),
783
+ unowned: z.enum(['clear', 'keep']).default('clear'),
784
+ })
785
+ .strict()
786
+ .optional(),
787
+ stateSize: z
788
+ .object({
789
+ min: z.number().int().min(1).max(1_000),
790
+ max: z.number().int().min(1).max(10_000).optional(),
791
+ })
792
+ .strict()
793
+ .optional()
794
+ .describe('Province unit: fragments below min provinces join a neighbour of the same owner.'),
795
+ remapReferences: z.enum(['refuse', 'override']).optional(),
796
+ borderQuality: borderQualityModeSchema.default('report'),
797
+ borderTargets: borderTargetsSchema.optional(),
798
+ })
799
+ .strict();
800
+ const repartitionOperation = z
801
+ .object({
802
+ ...operationBase,
803
+ kind: z.literal('repartition_states'),
804
+ countries: z.array(tagSchema).min(1).max(512).optional(),
805
+ stateIds: z.array(stateId).max(100_000).optional(),
806
+ provincesPerState: z
807
+ .object({
808
+ min: z.number().int().min(1).max(1_000).optional(),
809
+ target: z.number().int().min(1).max(1_000).optional(),
810
+ max: z.number().int().min(1).max(10_000).optional(),
811
+ })
812
+ .strict()
813
+ .optional(),
814
+ remapReferences: z.enum(['refuse', 'override']).optional(),
815
+ borderQuality: borderQualityModeSchema.default('report'),
816
+ borderTargets: borderTargetsSchema.optional(),
817
+ })
818
+ .strict();
637
819
  export const mapOperationSchema = z.discriminatedUnion('kind', [
638
820
  z
639
821
  .object({
@@ -690,29 +872,22 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
690
872
  ...operationBase,
691
873
  kind: z.literal('update_state'),
692
874
  stateId,
693
- changes: z
694
- .object({
695
- capital: provinceId
696
- .nullable()
697
- .optional()
698
- .describe('Assertion for the capital derived from the exact victoryPoints payload; never writes a state capital field'),
699
- manpower: z.number().min(0).optional(),
700
- category: z.string().min(1).optional(),
701
- resources: nonnegativeMap.optional(),
702
- stateBuildings: nonnegativeMap.optional(),
703
- owner: z.string().min(1).nullable().optional(),
704
- controller: z.string().min(1).nullable().optional(),
705
- cores: z.array(z.string().min(1)).max(10_000).optional(),
706
- claims: z.array(z.string().min(1)).max(10_000).optional(),
707
- victoryPoints: z
708
- .array(z.object({ provinceId, value: z.number().min(0) }).strict())
709
- .max(100_000)
710
- .optional(),
711
- provinceBuildings: z.record(z.string().regex(/^\d+$/u), nonnegativeMap).optional(),
712
- })
713
- .strict(),
875
+ changes: stateChangesSchema,
876
+ })
877
+ .strict(),
878
+ z
879
+ .object({
880
+ ...operationBase,
881
+ kind: z.literal('update_states'),
882
+ updates: z
883
+ .array(z.object({ stateId, changes: stateChangesSchema }).strict())
884
+ .min(1)
885
+ .max(5_000)
886
+ .describe('One entry per state, each with the changes update_state takes. The whole list is one validated rewrite; a state may appear once.'),
714
887
  })
715
888
  .strict(),
889
+ territoryOperation,
890
+ repartitionOperation,
716
891
  splitStateOperation('split_state'),
717
892
  createStateOperation,
718
893
  z
@@ -794,6 +969,10 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
794
969
  geography: geographyModelSchema
795
970
  .optional()
796
971
  .describe('Geography model version 1, inline: seas, lands, ranges, passes, volcanoes, plateaus, rivers, lakes, biomes, straits, sites and climate in normalized 0..1 world coordinates. Use geographyPath for models over a few hundred vertices. With preview:true it returns a model overlay and map-geography-report.json. Without preview the world is built from it: relief (ranges with passes, volcanoes, plateaus), rivers and lakes are synthesised; biomes, climate and fertility are not yet, and a model that declares them is blocked with MAP_GEOGRAPHY_SYNTHESIS_UNAVAILABLE.'),
972
+ synthesisCheck: z
973
+ .boolean()
974
+ .optional()
975
+ .describe('With preview:true and a geography: run only the relief and river synthesis and report every river that cannot reach its mouth, with the divide that stops it, without writing or planning the map. Refused without preview or without a geography.'),
797
976
  geographyPath: geographyPathSchema
798
977
  .optional()
799
978
  .describe('Workspace-relative .json file holding a geography model (at most 4 MiB). Alternative to the inline geography field; give one of them.'),
@@ -851,9 +1030,15 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
851
1030
  look: worldLookSchema.optional(),
852
1031
  colormap: worldColormapSchema.optional(),
853
1032
  partition: worldPartitionSchema.optional(),
1033
+ development: worldDevelopmentSchema.optional(),
1034
+ autoStraits: worldAutoStraitsSchema.optional(),
854
1035
  partitionTuning: worldPartitionTuningSchema
855
1036
  .optional()
856
1037
  .describe('Size bands and strengths of the feature-aware partition; refused with partition legacy.'),
1038
+ borderQuality: borderQualityModeSchema
1039
+ .optional()
1040
+ .describe('Measure how well the new world’s province, state, region and country borders follow rivers, slopes and shores. enforce blocks the write below the targets and needs the feature-aware partition; omitted measures nothing.'),
1041
+ borderTargets: borderTargetsSchema.optional(),
857
1042
  replaceBaseGame: z
858
1043
  .boolean()
859
1044
  .default(true)
@@ -1,6 +1,6 @@
1
1
  import { LATEST_PROTOCOL_VERSION } from '@modelcontextprotocol/sdk/types.js';
2
2
  export const PACKAGE_NAME = 'hoi4-agent-tools';
3
- export const PACKAGE_VERSION = '4.5.0';
3
+ export const PACKAGE_VERSION = '4.6.1';
4
4
  export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
5
5
  export const CONFIG_VERSION = 1;
6
6
  export const TRANSACTION_VERSION = 2;
package/docs/map.md CHANGED
@@ -113,7 +113,7 @@ A model is one object with `"version": 1` and any of these arrays. Unknown keys
113
113
 
114
114
  | Array | Feature and main fields |
115
115
  | ----------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
116
- | `seas` | `polygons` (outer rings), optional `holes`, `depthClass` (`shelf`, `open`, `abyss`), `ice` 0 to 1, `kind` (`ocean`, `sea`, `gulf`, `fjord`) |
116
+ | `seas` | `polygons` (outer rings), optional `holes`, `depthClass` (`shelf`, `open`, `abyss`), `ice` 0 to 1, `kind` (`ocean`, `sea`, `gulf`, `bay`, `fjord`) |
117
117
  | `lands` | `polygons`, `holes`, `kind` (`continent`, `island`, `archipelago`, `peninsula`), `region`, default `biome` and `fertility`, `elevationBase`, `coastDetail` |
118
118
  | `regions` | `polygons` (optional), `terrain` target shares, `density` (`provincesPerState` as `[smallest, usual, largest]`, `provinceAreaPx`) |
119
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`) |
@@ -161,7 +161,7 @@ The synthesis checks its own output and reports: `MAP_RELIEF_SYNTHESIZED` (info:
161
161
 
162
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
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.
164
+ Not synthesised yet: biomes, climate, fertility, tree and colour-map classes from the model, sea ice, and the render layers for relief and rivers. Straits, passes, walls, ports, development and names are described in [Crossings, ports, development and names from a model](#crossings-ports-development-and-names-from-a-model). 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.
165
165
 
166
166
  ## Inspect and navigate the complete map
167
167
 
@@ -265,7 +265,8 @@ Every operation has a unique `id` (1 to 256 characters), an optional `summary`,
265
265
  The supported kinds are:
266
266
 
267
267
  - whole maps: `create_world`;
268
- - states: `create_state`, `split_state`, `merge_states`, `move_state_provinces`, `update_state`;
268
+ - states: `create_state`, `split_state`, `merge_states`, `move_state_provinces`, `update_state`, `update_states`;
269
+ - countries and whole maps: `assign_territory`, `repartition_states`;
269
270
  - provinces: `create_province`, `split_province`, `merge_provinces`, `remove_province`, `update_province_definition`;
270
271
  - strategic regions: `move_region_provinces`;
271
272
  - identifiers and script references: `renumber_map_entity`, `remap_map_references`;
@@ -358,6 +359,69 @@ Pass a full `distribution` with `stateValues: "retain-in-current-states"` to lea
358
359
 
359
360
  Nothing is lost or duplicated: for any split, merge or move, the sums of manpower, each resource and each slot building over all states are the same before and after.
360
361
 
362
+ ## Assign territory to countries
363
+
364
+ `assign_territory` gives the land of a whole map to up to 512 countries in one call, so that their borders follow rivers, ranges and shores. `repartition_states` regroups the provinces of existing states along the same natural lines, and `update_states` edits up to 5,000 states in one validated rewrite. [ADR 0060](adr/0060-territory-assignment-service.md) records the design.
365
+
366
+ ```json
367
+ {
368
+ "operations": [
369
+ {
370
+ "id": "politics",
371
+ "kind": "assign_territory",
372
+ "unit": "province",
373
+ "seed": 7,
374
+ "countries": [
375
+ { "tag": "AAA", "weight": 3, "seeds": [{ "state": 41 }, { "x": 1810, "y": 902 }] },
376
+ {
377
+ "tag": "BBB",
378
+ "weight": 2,
379
+ "seeds": [{ "province": 3318 }],
380
+ "blocked": { "regionIds": [12] }
381
+ },
382
+ {
383
+ "tag": "CCC",
384
+ "weight": 1,
385
+ "seeds": [{ "state": 108 }],
386
+ "overlord": "AAA",
387
+ "regions": [4]
388
+ }
389
+ ],
390
+ "stateSize": { "min": 3, "max": 12 },
391
+ "ownership": { "cores": "owner", "claims": "clear" },
392
+ "borderQuality": "report"
393
+ }
394
+ ]
395
+ }
396
+ ```
397
+
398
+ | Field of a country | Meaning |
399
+ | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
400
+ | `tag`, `weight` | Three capital letters or digits, and the relative target size (default 1). |
401
+ | `seeds` | `{ state }`, `{ province }` or a pixel `{ x, y }` of the province map; the first is the capital. A seed in the sea is refused. |
402
+ | `forced`, `blocked` | `{ stateIds, provinceIds, regionIds }`: land the country always or never owns. `regionIds` are strategic regions. |
403
+ | `regions` | Strategic regions the country prefers to grow in (a step into another country's preferred region costs more). |
404
+ | `overlord`, `mergeInto` | A subject is grown inside its overlord's territory. `mergeInto` relabels the finished territory to another listed country and keeps its tag as a core. |
405
+ | `cores`, `claims` | Extra states the country holds a core on or claims. |
406
+
407
+ Top-level fields: `unit` (`state`, the default, assigns whole states; `province` assigns provinces and regroups the states a border cuts), `scope` (`stateIds`, `regionIds`, `owners`: only these states take part), `unowned` (land nobody owns), `barriers` (`natural` or `none`, which grows on distance alone), `rules.connectedMainland` (default true) and `rules.islandPolicy` (`one-owner`, `nearest` or `unowned` for landmasses without a seed), `smoothing` (`passes` 0 to 20, `maxShareChange`), `shareTolerance` (default 0.15), `seed` (the same seed gives the same borders), `ownership` (`controller`: `owner` or `keep`; `cores`: `keep`, `owner` or `add-owner`; `claims`: `keep` or `clear`; `unowned`: `clear` or `keep`), `stateSize` (province unit: fragments below `min` provinces join a neighbour of the same owner and strategic region), `remapReferences` (`refuse` or `override`, as for `merge_states`), `borderQuality` and `borderTargets`.
408
+
409
+ How it works: a multi-source shortest-path growth from the seeds over the province or state graph, where a step costs the distance between centres times `1 + 6 * barrier` (the barrier is the strength of the rivers, slopes, lake shores and terrain changes along the shared border, measured by the metric of [Border naturalness](#border-naturalness)); country multipliers are corrected until sizes are within the tolerance of their targets; a country cut apart on its capital's landmass is joined by a corridor; and a local search moves boundary atoms when that shortens the artificial border without disconnecting the donor or leaving the size tolerance. Targets follow the weights, corrected for what the land allows (a country alone on an island owns the island).
410
+
411
+ The province unit writes the new borders exactly: each state is split by owner, small fragments are merged, and states are rewritten in one pass. Population, resources and slot buildings follow the land by pixel share in whole units (every total is conserved), levels keep their value, victory points and province buildings follow their provinces, cores and claims are the union of the states that contribute, and new states are named `STATE_<id>` in `hoi4_agent_map_l_english.yml` after the state they were cut from, with a number. A state may hold provinces of one strategic region only; dated history that would be lost or rewritten, and provinces without geometry, refuse the operation.
412
+
413
+ `repartition_states` takes `countries` (owners), `stateIds`, `provincesPerState` (`min` 3, `target` 6 and `max` 10 by default) and `remapReferences`. States are clustered per owner and strategic region; a cluster equal to an existing state is left alone.
414
+
415
+ `update_states` takes `updates: [{ stateId, changes }]` with the changes of `update_state`. The index is built once; a state may appear once, and an unknown state refuses the whole list.
416
+
417
+ `borderQuality` is `report` by default: the result adds `MAP_BORDER_QUALITY_MEASURED` with the enrichment, natural share and straightness of the proposed country and state borders, and the artifact `map-territory-report.json` holds every number. `enforce` blocks the write when a layer misses the targets (enrichment 1.6; straightness 0.12 for countries and 0.25 for states; override with `borderTargets`) with `MAP_TERRITORY_BORDER_ARTIFICIAL`; `off` skips it. `data.territory` returns, per operation, the sizes against targets (worst eight countries), the border score, the same seeds grown on distance alone (`distanceOnly`), and the counts of states changed, created and removed. Previews work as for any rewrite and write nothing.
418
+
419
+ Unowned land: states in `unowned`, and landmasses without a seed under `islandPolicy: "unowned"`, are not grown into. With `ownership.unowned: "clear"` (the default) their owner is removed, and their controller too when `controller` is `owner`; `"keep"` leaves them as they were. States outside `scope` are never edited. Provinces that belong to no state cannot be owned and are left out.
420
+
421
+ Refusals name the cause and the state or province of the request: `MAP_TERRITORY_SEED_IN_WATER`, `_STATE_UNKNOWN`, `_PROVINCE_UNKNOWN`, `_REGION_UNKNOWN`, `_SCOPE_UNKNOWN`, `_POINT_OUTSIDE_MAP`, `_PROVINCE_WITHOUT_STATE`, `_SEED_MISSING` (a country with no seed), `_SEED_UNAVAILABLE` (a seed or forced land is unowned, outside the scope or outside its overlord), `_FORCED_CONFLICT` (two countries claim one state or province), `_FORCED_BLOCKED` (forced or a seed and blocked for the same country), `_FORCED_UNREACHABLE` (blocked or forced land cuts a seed off from its capital), `_TAG_INVALID`, `_TAG_DUPLICATE`, `_WEIGHT_INVALID`, `_TOO_MANY_COUNTRIES` (above 512), `_TOO_MANY_SEEDS` (above 64), `_OVERLORD_INVALID` (unknown, itself or a cycle), `_MERGE_INVALID`, `_OPTION_INVALID`, `_STATE_OVERLAP`, `_STATE_SIZE_INVALID`, `_STATE_SIZE_NEEDS_PROVINCE_UNIT`, `_NO_LAND`, `_MAP_MISSING`, `_NOTHING_TO_REPARTITION`, and `MAP_UPDATE_STATES_DUPLICATE` for a state listed twice in `update_states`. The schema refuses more than 512 countries, more than 64 seeds, an empty seed list, a tag that is not three capital letters or digits, a weight of zero or less, and more than 5,000 `update_states` entries before anything is planned. Warnings: `MAP_TERRITORY_SHARE_OFF` (a country is more than the tolerance from its target) and `MAP_TERRITORY_DISCONNECTED`; info: `MAP_TERRITORY_ASSIGNED`, `MAP_TERRITORY_REPARTITIONED` and `MAP_TERRITORY_NO_FEATURES` (no height, river or terrain bitmap, so borders fall back to distance), `MAP_TERRITORY_STATE_SIZE_UNMET` (a fragment below `stateSize.min` has no neighbour of the same owner and strategic region to join) and `MAP_TERRITORY_STATE_SKIPPED` (`repartition_states` leaves states that span several strategic regions or share provinces).
422
+
423
+ Measured on project-owned synthetic fixtures (ADR 0060): country-border enrichment 3.1 to 3.6 against 1.2 to 2.5 for the same seeds grown on distance alone on a partitioned world with rivers and ridges; on a generated total-conversion map of 13.1 million pixels with about 300 countries and 1,500 states, 15 seconds by state and 26 seconds by province.
424
+
361
425
  ## Other map changes
362
426
 
363
427
  `hoi4.map_rewrite` also moves and merges states, moves provinces between strategic regions, merges or removes provinces, changes province definitions and types, adds or removes normal and special adjacency, and updates supply, railway, building, unit, weather, and entity-locator records. Send one ordered operation list when several connected changes belong together.
@@ -397,6 +461,28 @@ Keys that take exactly one value (`capital`, `state`, `to_state`, `start_state`,
397
461
 
398
462
  `merge_states`, `create_state` and `split_state` can do this themselves: add `"remapReferences": "refuse"` (or `"override"`, which copies the game files that need it into the mod) and the merged-away states are renamed to the survivor, or the source state's references are extended to the new part, in the same plan.
399
463
 
464
+ ### Crossings, ports, development and names from a model
465
+
466
+ A world built from a geography also gets its gameplay facts from the model (ADR [0062](adr/0062-straits-passes-and-impassable-ranges.md) and [0063](adr/0063-geography-driven-ports-development-names-and-railways.md)). A world without a geography is unchanged.
467
+
468
+ | Model feature | What the world gets |
469
+ | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
470
+ | `strait` | A named `sea` row in `adjacencies.csv` (land to land through a sea province; sea to sea through land when the path crosses an isthmus) and a rule in `adjacency_rules.txt` whose access follows `rule`: `open`, `army-closed` or `navy-closed`. The closest shores give the crossing line. |
471
+ | `range` with `barrier: "impassable"` | `impassable` rows for every pair of land provinces whose border crosses the range's path, except at its passes. The partition cuts along the wall, so provinces do not straddle it. |
472
+ | `pass` | A gap in the wall: the province pairs whose border passes through it carry no row. `fort` above 0 puts `bunker = level` on both provinces of the gate (the same for `fort-line` ranges, which write no rows). |
473
+ | `lake` with `gameplay: "inland-sea"` | Sea provinces, so ships and naval bases can use it; a canal row joins a sealed one to the nearest ocean unless a declared strait does. Other lakes stay `lake` provinces and cannot hold a port. |
474
+ | `site` | Victory points and names for capital, city, port and fortress sites (importance 1 to 5 gives 3 to 20 points); a port site holds a port; a `mine` site gives its `resource` (steel by default); a `country` anchor chooses that country's capital. |
475
+ | fertility, rivers, coast, passes | Population, state category, infrastructure, factories and the harbour score of each coastal province, with no random rolls. |
476
+ | names | State, strategic region, victory-point and lake-province names from sites, lake shores, river valleys, ranges, regions, lands and seas by containment; shared names get a compass adjective. |
477
+
478
+ Options of `create_world`: `development: { model }` (`geography`, the default for a geography world, or `legacy` for the random economy) and `autoStraits` (`true` or `{ maxGapPx }`, default 24 pixels, about 170 km) to also write a rule-less crossing for every narrow channel the model did not declare. Both need a geography (`MAP_DEVELOPMENT_REQUIRES_GEOGRAPHY`, `MAP_AUTO_STRAITS_REQUIRES_GEOGRAPHY`).
479
+
480
+ Railways of a geography world follow a terrain-aware cost (ground, climb across the border, rivers, gates, model roads) and never cross a wall except at a gate; trunk lines of level 2 reach the cities of 8 or more victory points. The supply-node rule is unchanged.
481
+
482
+ Diagnostics raised when a world is created (they need the model): `MAP_CROSSINGS_PLANNED`, `MAP_DEVELOPMENT_PLANNED` and `MAP_INLAND_SEA_CREATED` (info), `MAP_STRAIT_UNRESOLVED`, `MAP_STRAIT_DUPLICATE`, `MAP_STRAIT_NOT_NARROW`, `MAP_PASS_BLOCKED`, `MAP_RANGE_NOT_CLOSED`, `MAP_INLAND_SEA_SEALED`, `MAP_PORT_LAKE_UNSUPPORTED`, `MAP_PORT_SITE_NOT_COASTAL`, `MAP_SITE_UNPLACED` and `MAP_NAME_PLACEHOLDER` (warnings; the last counts the states, strategic regions and victory points still called "District N", "Region N" or "Settlement N"), and `MAP_IMPASSABLE_ROW_LIMIT` (refusal above 2,000 wall rows). Raised on any map by `hoi4.map_inspect`: `MAP_ADJACENCY_RULE_MISSING`, `_DUPLICATE` and `_INVALID` (errors), `MAP_ADJACENCY_RULE_UNUSED`, `MAP_IMPASSABLE_NOT_ADJACENT` and `MAP_UNIT_POSITION_MISSING` (information, or warnings when they are a pattern), `MAP_IMPASSABLE_NOT_LAND`, `MAP_ADJACENCY_ROW_LIMIT`, `MAP_STRAIT_NOT_NARROW` and `MAP_RAILWAY_CROSSES_IMPASSABLE` (warnings). The installed game raises nothing above information.
483
+
484
+ Not done: whole provinces inside a massif are not made impassable states, the engine's handling of impassable rows by supply and the AI and of a naval base on an inland sea is not verified, and `positions.txt` stays empty as in the base game. Measured on the project's 5,120 x 2,560 synthetic model with about 6,000 provinces, the gameplay layer adds about 6 seconds to a 30 second `create_world` and the whole run peaks near 0.8 GiB.
485
+
400
486
  ## Positions and supply
401
487
 
402
488
  `regenerate_map_positions` places building, unit-stack and weather positions from province geometry. By default (`mode: missing`) it only adds what is absent: positions for each building model a state or province lacks (from `show_on_map` and the spawn points in `common/buildings`), a full unit-stack set for provinces without one, and a small and a big weather object for strategic regions without one. `mode: replace` rewrites the positions of the selected `stateIds`, `provinceIds` and `regionIds`. Interior points keep away from province borders, naval bases face the sea province they open onto, and floating harbours sit on the water and name their land province. `positions` limits it to `buildings`, `units` or `weather`; heights come from the heightmap.
@@ -465,6 +551,16 @@ Every `create_world` reports the country tags declared after the write (`MAP_WOR
465
551
 
466
552
  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.
467
553
 
554
+ ### Rivers, carved valleys and the river check
555
+
556
+ A river the model draws is authoritative over the synthesised relief (ADR 0071). Where its course crosses a divide more than 22 height units above its bed (the cone of a volcano, a foothill, the tip of a range), the bed is cut through it as a gorge, and each crossing is reported as the warning `MAP_RIVER_CARVED_CROSSING` with the river, the climb and the divide (world pixel, height, and the nearest range or the volcano whose cone it crosses). Move the river or the feature in the model if the gorge is unwanted.
557
+
558
+ Only a course that cannot exist is refused with `MAP_RIVER_UNREACHABLE_MOUTH`: a mouth on another landmass or not reachable over land, an unreachable source or via point, a lake without a free shore. The error lists every such river in `details.rivers` with its reason, or `blockedBy` another failed river.
559
+
560
+ A declared pass is a saddle lower than the crest on both sides (ADR 0072): the heights in its corridor are capped at the saddle level after all relief is composed, so a volcano cone or plateau on the pass is cut down there (move the volcano if the cone should stay whole). Another range's body is not touched.
561
+
562
+ To check all of this without a write, call `hoi4.map_rewrite` with `preview: true` and `synthesisCheck: true` on the `create_world` operation: it runs the relief and river synthesis only, writes nothing and returns `MAP_SYNTHESIS_CHECK_READY` or `MAP_SYNTHESIS_CHECK_BLOCKED` with `data.synthesisCheck` (unreachable rivers, carved crossings and passes that are not lower than their crest). It does not run the partition, the countries or the climate stage (ADR 0070). A world that fails to generate returns only its own blockers, never the findings of the map it would replace.
563
+
468
564
  ### Feature-aware provinces, states and strategic regions
469
565
 
470
566
  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):
@@ -490,7 +586,7 @@ By default provinces grow evenly from spread seeds and states and regions grow b
490
586
  }
491
587
  ```
492
588
 
493
- 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).
589
+ 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), or ask for it in the same call with `borderQuality`: `report` adds the measured province, state, region and country borders of the new world to the result, and `enforce` blocks the write below the targets (it needs the feature-aware partition and is refused with `legacy`, whose borders are at chance level). Countries of a feature-aware world without a reference picture are grown along natural lines by the territory service ([Assign territory to countries](#assign-territory-to-countries)).
494
590
 
495
591
  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.
496
592
 
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
- "version": "4.5.0",
3
+ "version": "4.6.1",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hoi4-agent-tools",
9
- "version": "4.5.0",
9
+ "version": "4.6.1",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@fontsource-variable/roboto": "5.3.0",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
3
  "mcpName": "io.github.klimPaskov/hoi4-agent-tools",
4
- "version": "4.5.0",
4
+ "version": "4.6.1",
5
5
  "description": "Build HOI4 mods: focus trees, events, decisions, worlds, GUI previews, docs and AI balance.",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/configuration.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/configuration.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/continuous-focus-palette.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/continuous-focus-palette.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/custom-weighted-pool.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/custom-weighted-pool.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/decision-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/decision-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/error-log-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/error-log-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/event-feature-manifest.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/event-feature-manifest.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/focus-plan.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/focus-plan.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/focus-planning-sidecar.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/focus-planning-sidecar.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/geography-model.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/geography-model.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -121,6 +121,7 @@
121
121
  "ocean",
122
122
  "sea",
123
123
  "gulf",
124
+ "bay",
124
125
  "fjord"
125
126
  ]
126
127
  }
@@ -1006,6 +1007,17 @@
1006
1007
  },
1007
1008
  "lake": {
1008
1009
  "$ref": "#/$defs/__schema6"
1010
+ },
1011
+ "resource": {
1012
+ "type": "string",
1013
+ "enum": [
1014
+ "steel",
1015
+ "aluminium",
1016
+ "tungsten",
1017
+ "chromium",
1018
+ "oil",
1019
+ "rubber"
1020
+ ]
1009
1021
  }
1010
1022
  },
1011
1023
  "required": [
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/gui-animation-source.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/gui-animation-source.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/gui-helper.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/gui-helper.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/gui-scenario.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.6.1/schemas/gui-scenario.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {