hoi4-agent-tools 4.4.0 → 4.5.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 (62) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/hoi4_agent_tools/core/managed-outputs.js +26 -6
  3. package/dist/hoi4_agent_tools/core/source/encoding.js +56 -2
  4. package/dist/hoi4_agent_tools/core/source/parser.js +16 -1
  5. package/dist/hoi4_agent_tools/focus/drift.js +34 -0
  6. package/dist/hoi4_agent_tools/focus/service.js +9 -3
  7. package/dist/hoi4_agent_tools/focus/tool-operations.js +2 -0
  8. package/dist/hoi4_agent_tools/map/model.js +2 -1
  9. package/dist/hoi4_agent_tools/map/operations.js +173 -6
  10. package/dist/hoi4_agent_tools/map/reference-preparation.js +4 -1
  11. package/dist/hoi4_agent_tools/map/service.js +15 -0
  12. package/dist/hoi4_agent_tools/map/tool-operations.js +41 -1
  13. package/dist/hoi4_agent_tools/map/world-context.js +23 -0
  14. package/dist/hoi4_agent_tools/map/world-outputs.js +22 -10
  15. package/dist/hoi4_agent_tools/map/world-realms.js +152 -0
  16. package/dist/hoi4_agent_tools/map/world.js +100 -39
  17. package/dist/hoi4_agent_tools/schemas/map.js +71 -13
  18. package/dist/hoi4_agent_tools/version.js +1 -1
  19. package/docs/focus.md +6 -0
  20. package/docs/map.md +34 -0
  21. package/npm-shrinkwrap.json +2 -2
  22. package/package.json +1 -1
  23. package/schemas/configuration.schema.json +1 -1
  24. package/schemas/continuous-focus-palette.schema.json +1 -1
  25. package/schemas/custom-weighted-pool.schema.json +1 -1
  26. package/schemas/decision-inspect-input.schema.json +1 -1
  27. package/schemas/error-log-input.schema.json +1 -1
  28. package/schemas/event-feature-manifest.schema.json +1 -1
  29. package/schemas/focus-plan.schema.json +1 -1
  30. package/schemas/focus-planning-sidecar.schema.json +1 -1
  31. package/schemas/geography-model.schema.json +1 -1
  32. package/schemas/gui-animation-source.schema.json +1 -1
  33. package/schemas/gui-helper.schema.json +1 -1
  34. package/schemas/gui-scenario.schema.json +1 -1
  35. package/schemas/helper-expansion-request.schema.json +1 -1
  36. package/schemas/helper-expansion-summary.schema.json +1 -1
  37. package/schemas/impact-inspect-input.schema.json +1 -1
  38. package/schemas/map-operation.schema.json +725 -589
  39. package/schemas/mechanic-test-input.schema.json +1 -1
  40. package/schemas/mod-index-input.schema.json +1 -1
  41. package/schemas/operation-result.schema.json +1 -1
  42. package/schemas/package-check-input.schema.json +1 -1
  43. package/schemas/package-manifest.schema.json +1 -1
  44. package/schemas/probability-analysis-result.schema.json +1 -1
  45. package/schemas/probability-compare-input.schema.json +1 -1
  46. package/schemas/probability-evaluate-input.schema.json +1 -1
  47. package/schemas/probability-inspect-input.schema.json +1 -1
  48. package/schemas/probability-render-input.schema.json +1 -1
  49. package/schemas/probability-scenario-set.schema.json +1 -1
  50. package/schemas/probability-sequence-input.schema.json +1 -1
  51. package/schemas/probability-simulate-input.schema.json +1 -1
  52. package/schemas/probability-sweep-input.schema.json +1 -1
  53. package/schemas/reference-context-input.schema.json +1 -1
  54. package/schemas/reference-read-input.schema.json +1 -1
  55. package/schemas/reference-search-input.schema.json +1 -1
  56. package/schemas/reference-sheets.schema.json +1 -1
  57. package/schemas/scenario-suite.schema.json +1 -1
  58. package/schemas/scenario-test-input.schema.json +1 -1
  59. package/schemas/script-validate-input.schema.json +1 -1
  60. package/schemas/shared-scenario.schema.json +1 -1
  61. package/schemas/source-lookup-input.schema.json +1 -1
  62. package/server.json +2 -2
@@ -24,6 +24,59 @@ const rgbSchema = z
24
24
  })
25
25
  .strict();
26
26
  const pointSchema = z.object({ x: z.number().int().min(0), y: z.number().int().min(0) }).strict();
27
+ const countryTagSchema = z.string().regex(/^[A-Z][A-Z0-9]{2}$/u);
28
+ const realmPolygonSchema = z.array(pointSchema).min(3).max(10_000);
29
+ const realmPolygonsSchema = z.array(realmPolygonSchema).min(1).max(64);
30
+ export const workspaceRealmSchema = z
31
+ .object({
32
+ tag: countryTagSchema,
33
+ capital: pointSchema.describe('World pixel, top-left origin, on land; the capital province.'),
34
+ seeds: z
35
+ .array(pointSchema)
36
+ .max(256)
37
+ .optional()
38
+ .describe('Further world pixels whose provinces the country must own.'),
39
+ claim: realmPolygonsSchema
40
+ .optional()
41
+ .describe('Polygons in world pixels; land provinces more than half inside belong to the country.'),
42
+ exclude: realmPolygonsSchema
43
+ .optional()
44
+ .describe('Polygons in world pixels; land provinces more than half inside never belong to it.'),
45
+ weight: z
46
+ .number()
47
+ .min(0.05)
48
+ .max(100)
49
+ .optional()
50
+ .describe('Relative share of the land claims leave free; default 1.'),
51
+ })
52
+ .strict();
53
+ export const workspaceCountriesSchema = z
54
+ .object({
55
+ source: z.literal('workspace'),
56
+ realms: z.array(workspaceRealmSchema).min(1).max(512),
57
+ unowned: z
58
+ .array(realmPolygonSchema)
59
+ .max(256)
60
+ .optional()
61
+ .describe('Polygons in world pixels whose land provinces no country owns.'),
62
+ islands: z
63
+ .enum(['nearest', 'one-owner', 'unowned'])
64
+ .default('nearest')
65
+ .describe('Landmasses no country reaches: every province to the nearest capital, the whole landmass to one country, or unowned.'),
66
+ })
67
+ .strict()
68
+ .superRefine((value, context) => {
69
+ const seen = new Set();
70
+ for (const [index, realm] of value.realms.entries()) {
71
+ if (seen.has(realm.tag))
72
+ context.addIssue({
73
+ code: 'custom',
74
+ path: ['realms', index, 'tag'],
75
+ message: `Country ${realm.tag} is listed twice`,
76
+ });
77
+ seen.add(realm.tag);
78
+ }
79
+ });
27
80
  const operationBase = {
28
81
  id: identifier,
29
82
  summary: z.string().max(1000).optional(),
@@ -760,19 +813,24 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
760
813
  landProvincesPerRegion: z.number().int().min(2).max(400).optional(),
761
814
  seaProvincesPerRegion: z.number().int().min(1).max(200).optional(),
762
815
  countries: z
763
- .array(z
764
- .object({
765
- tag: z.string().regex(/^[A-Z][A-Z0-9]{2}$/u),
766
- name: worldName,
767
- adjective: worldName.optional(),
768
- color: rgbSchema.optional(),
769
- share: z.number().min(0.05).max(100).optional(),
770
- ideology: z.enum(['democratic', 'communism', 'fascism', 'neutrality']).optional(),
771
- })
772
- .strict())
773
- .min(1)
774
- .max(200)
775
- .optional(),
816
+ .union([
817
+ z
818
+ .array(z
819
+ .object({
820
+ tag: countryTagSchema,
821
+ name: worldName,
822
+ adjective: worldName.optional(),
823
+ color: rgbSchema.optional(),
824
+ share: z.number().min(0.05).max(100).optional(),
825
+ ideology: z.enum(['democratic', 'communism', 'fascism', 'neutrality']).optional(),
826
+ })
827
+ .strict())
828
+ .min(1)
829
+ .max(200),
830
+ workspaceCountriesSchema,
831
+ ])
832
+ .optional()
833
+ .describe('Countries the world generates and writes (an array), or { source: "workspace", realms } for countries the workspace already declares: the world then places them from their capital, seeds, claims and exclusions and writes no country, flag, unit, country history, country name or bookmark file.'),
776
834
  continentNames: z.array(worldName).max(12).optional(),
777
835
  startDate: z
778
836
  .string()
@@ -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.4.0';
3
+ export const PACKAGE_VERSION = '4.5.0';
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/focus.md CHANGED
@@ -168,6 +168,12 @@ Branch names, lane assignments, working labels, and other planning-only data may
168
168
 
169
169
  If malformed or unsupported script makes a requested change ambiguous, the rewrite stops and explains the blocker instead of guessing.
170
170
 
171
+ ### Stale source and source encoding
172
+
173
+ A rewrite starts from a plan returned by `hoi4.focus_inspect`. If the source file changed in meaning after that inspection, `hoi4.focus_rewrite` refuses with `FOCUS_SOURCE_CHANGED_SINCE_PLAN` and writes nothing, because applying the plan would overwrite the other change with the plan's older values. Call `hoi4.focus_inspect` again, reapply the intended edit to the new plan, and retry. There is no override. A change that only touches comments or formatting does not trigger the refusal, and neither does a source that already equals the plan.
174
+
175
+ Source files are read as UTF-8 (with or without a byte order mark) and, when they are not valid UTF-8, as Windows-1252. Windows-1252 leaves the bytes 0x81, 0x8D, 0x8F, 0x90 and 0x9D undefined, so a file containing any of them cannot be edited without changing those bytes. Inspection still works and reports the warning `SOURCE_ENCODING_NOT_ROUND_TRIPPABLE`; every rewrite of that file is refused with the same code. Convert the file to UTF-8, or replace those bytes, and retry.
176
+
171
177
  ## What inspection checks
172
178
 
173
179
  Inspection and rendering can report missing references, invalid prerequisite structure, duplicate or overlapping positions, insufficient spacing, long or crossing connectors, branch asymmetry, route conflicts, missing localisation or sprites, weak terminal branches, repeated rewards, and missing AI metadata. National-tree inspection also returns the exact `continuous_focus_position` coordinates and linked palette IDs in its compact result, so an agent can identify and move the continuous-focus area without opening the full artifact. Renders are offline review artifacts, not game screenshots.
package/docs/map.md CHANGED
@@ -429,6 +429,40 @@ Width and height are multiples of 256 with at most 13,238,272 pixels. Land comes
429
429
 
430
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.
431
431
 
432
+ Each write records its generated files in `.hoi4-agent-world.json`. A later `create_world` deletes the recorded files it no longer writes (retires them) in the same transaction and refuses with `MANAGED_OUTPUT_EDITED` when a recorded file was edited since. `MAP_WORLD_OUTPUTS_RECONCILED` and `data.outputs` give the counts of written, retired and released files with up to 200 paths each; `map-world-outputs.json` lists them all.
433
+
434
+ ### Countries the mod already owns
435
+
436
+ A mod that writes its own country tags, country files, histories, orders of battle, flags and names gives `countries` as an object instead of an array (ADR 0069):
437
+
438
+ ```json
439
+ "countries": {
440
+ "source": "workspace",
441
+ "realms": [
442
+ { "tag": "AUR", "capital": { "x": 812, "y": 340 }, "weight": 2 },
443
+ {
444
+ "tag": "BOR",
445
+ "capital": { "x": 1210, "y": 300 },
446
+ "seeds": [{ "x": 1300, "y": 420 }],
447
+ "claim": [[{ "x": 1180, "y": 250 }, { "x": 1290, "y": 250 }, { "x": 1290, "y": 380 }]],
448
+ "exclude": [[{ "x": 1000, "y": 500 }, { "x": 1100, "y": 500 }, { "x": 1100, "y": 600 }]]
449
+ }
450
+ ],
451
+ "unowned": [[{ "x": 600, "y": 700 }, { "x": 680, "y": 700 }, { "x": 680, "y": 760 }]],
452
+ "islands": "nearest"
453
+ }
454
+ ```
455
+
456
+ Points and polygon vertices are world pixels with the origin at the top left. A capital or seed names the land province under it; a land province with more than half its pixels inside a claim belongs to that country, inside an exclusion never does, and inside `unowned` belongs to nobody. Countries grow from their capitals, seeds and claims by `weight` (default 1). Land that growth does not reach goes, on a landmass a country already holds, to the nearest country that may own it; a landmass no country starts on follows `islands`: `nearest` (each province to the nearest capital), `one-owner` (the whole landmass to the country nearest its centre) or `unowned`. At most 512 countries.
457
+
458
+ Every tag must be declared by an active `common/country_tags` file other than the world's own (`MAP_WORLD_REALM_TAG_UNKNOWN`) and not only as a dynamic tag (`MAP_WORLD_REALM_TAG_DYNAMIC`). A point outside the world (`MAP_WORLD_REALM_POINT_OUTSIDE`) or in water (`MAP_WORLD_REALM_POINT_WATER`), a point in unowned land, in the country's own exclusion or on another country's point (`MAP_WORLD_REALM_POINT_CONFLICT`), and claims that overlap each other or unowned land (`MAP_WORLD_REALM_CLAIM_OVERLAP`) are refused with the tag and position. A reference picture's political colours are not used with this form.
459
+
460
+ The world then writes no country tag file, country file, `colors.txt`, country history, order of battle, flag, country name or bookmark. Unowned land forms states with no `owner`, `controller` or `add_core_of` (`MAP_WORLD_STATE_UNOWNED`, and `MAP_STATE_OWNER_MISSING` for each); whether the game build accepts unowned land is the mod's to verify. `MAP_WORLD_REALMS_PLACED`, `data.realms` and `map-world-realms.json` give each country's capital state and province, states, provinces and land share against its target. The country histories stay the mod's: a `capital` in a state the country does not own is the existing warning `MAP_COUNTRY_CAPITAL_NOT_OWNED`.
461
+
462
+ A recorded country file of an earlier generated world that the mod has since edited is released: left in place, dropped from `.hoi4-agent-world.json` and listed as released (`MAP_WORLD_OUTPUT_RELEASED`). Unedited ones are retired. Other edited recorded files still refuse. To keep a file the mod changed, leave its recorded hash as the world wrote it.
463
+
464
+ Every `create_world` reports the country tags declared after the write (`MAP_WORLD_TAG_COUNT`: static, written by the world, dynamic, total) and warns above 439 (`MAP_WORLD_TAG_COUNT_HIGH`), the count at which one measured game build overflowed its flag sheet; this is an observation, not a documented limit, and never blocks.
465
+
432
466
  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.
433
467
 
434
468
  ### Feature-aware provinces, states and strategic regions
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
- "version": "4.4.0",
3
+ "version": "4.5.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hoi4-agent-tools",
9
- "version": "4.4.0",
9
+ "version": "4.5.0",
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.4.0",
4
+ "version": "4.5.0",
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.4.0/schemas/configuration.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/continuous-focus-palette.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/custom-weighted-pool.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/decision-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/error-log-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/event-feature-manifest.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/focus-plan.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/focus-planning-sidecar.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/geography-model.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/geography-model.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.4.0/schemas/gui-animation-source.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/gui-helper.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/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.4.0/schemas/gui-scenario.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/gui-scenario.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.4.0/schemas/helper-expansion-request.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/helper-expansion-request.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.4.0/schemas/helper-expansion-summary.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/helper-expansion-summary.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.4.0/schemas/impact-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.5.0/schemas/impact-inspect-input.schema.json",
3
3
  "$schema": "https://json-schema.org/draft/2020-12/schema",
4
4
  "type": "object",
5
5
  "properties": {