hoi4-agent-tools 4.0.5 → 4.1.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 (80) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/README.md +2 -0
  3. package/dist/hoi4_agent_tools/core/bounded-source.js +210 -0
  4. package/dist/hoi4_agent_tools/core/diagnostic-rollup.js +91 -0
  5. package/dist/hoi4_agent_tools/core/domain-validation.js +12 -0
  6. package/dist/hoi4_agent_tools/core/engine.js +2 -1
  7. package/dist/hoi4_agent_tools/core/job-executor.js +9 -0
  8. package/dist/hoi4_agent_tools/core/managed-outputs.js +72 -0
  9. package/dist/hoi4_agent_tools/core/operation-result.js +28 -2
  10. package/dist/hoi4_agent_tools/core/scanner.js +17 -0
  11. package/dist/hoi4_agent_tools/core/server-state.js +9 -1
  12. package/dist/hoi4_agent_tools/core/temporary-directories.js +5 -1
  13. package/dist/hoi4_agent_tools/core/transaction-canonical.js +43 -0
  14. package/dist/hoi4_agent_tools/core/transaction-limits.js +7 -2
  15. package/dist/hoi4_agent_tools/core/transactions.js +355 -111
  16. package/dist/hoi4_agent_tools/map/derived.js +21 -6
  17. package/dist/hoi4_agent_tools/map/job-operations.js +9 -3
  18. package/dist/hoi4_agent_tools/map/operations.js +72 -21
  19. package/dist/hoi4_agent_tools/map/reference-labels.js +238 -0
  20. package/dist/hoi4_agent_tools/map/reference-preparation.js +247 -0
  21. package/dist/hoi4_agent_tools/map/reference-world.js +1097 -0
  22. package/dist/hoi4_agent_tools/map/render.js +9 -1
  23. package/dist/hoi4_agent_tools/map/service.js +222 -18
  24. package/dist/hoi4_agent_tools/map/tool-operations.js +71 -2
  25. package/dist/hoi4_agent_tools/map/world-context.js +148 -0
  26. package/dist/hoi4_agent_tools/map/world-drainage.js +273 -0
  27. package/dist/hoi4_agent_tools/map/world-outputs.js +55 -0
  28. package/dist/hoi4_agent_tools/map/world-preview-style.js +157 -0
  29. package/dist/hoi4_agent_tools/map/world-preview.js +230 -0
  30. package/dist/hoi4_agent_tools/map/world-provinces.js +524 -0
  31. package/dist/hoi4_agent_tools/map/world.js +374 -253
  32. package/dist/hoi4_agent_tools/mcp/tools/map.js +15 -2
  33. package/dist/hoi4_agent_tools/schemas/map-requests.js +4 -0
  34. package/dist/hoi4_agent_tools/schemas/map.js +103 -10
  35. package/dist/hoi4_agent_tools/schemas/transaction.js +20 -3
  36. package/dist/hoi4_agent_tools/version.js +2 -2
  37. package/docs/images/reference-maps/political.png +0 -0
  38. package/docs/images/reference-maps/sketch.png +0 -0
  39. package/docs/map.md +45 -0
  40. package/docs/third-party-notices.md +6 -0
  41. package/npm-shrinkwrap.json +124 -2
  42. package/package.json +4 -1
  43. package/schemas/configuration.schema.json +1 -1
  44. package/schemas/continuous-focus-palette.schema.json +1 -1
  45. package/schemas/custom-weighted-pool.schema.json +1 -1
  46. package/schemas/decision-inspect-input.schema.json +1 -1
  47. package/schemas/error-log-input.schema.json +1 -1
  48. package/schemas/event-feature-manifest.schema.json +1 -1
  49. package/schemas/focus-plan.schema.json +1 -1
  50. package/schemas/focus-planning-sidecar.schema.json +1 -1
  51. package/schemas/gui-animation-source.schema.json +1 -1
  52. package/schemas/gui-helper.schema.json +1 -1
  53. package/schemas/gui-scenario.schema.json +1 -1
  54. package/schemas/helper-expansion-request.schema.json +1 -1
  55. package/schemas/helper-expansion-summary.schema.json +1 -1
  56. package/schemas/impact-inspect-input.schema.json +1 -1
  57. package/schemas/map-operation.schema.json +373 -70
  58. package/schemas/mechanic-test-input.schema.json +1 -1
  59. package/schemas/mod-index-input.schema.json +1 -1
  60. package/schemas/operation-result.schema.json +1 -1
  61. package/schemas/package-check-input.schema.json +1 -1
  62. package/schemas/package-manifest.schema.json +1 -1
  63. package/schemas/probability-analysis-result.schema.json +1 -1
  64. package/schemas/probability-compare-input.schema.json +1 -1
  65. package/schemas/probability-evaluate-input.schema.json +1 -1
  66. package/schemas/probability-inspect-input.schema.json +1 -1
  67. package/schemas/probability-render-input.schema.json +1 -1
  68. package/schemas/probability-scenario-set.schema.json +1 -1
  69. package/schemas/probability-sequence-input.schema.json +1 -1
  70. package/schemas/probability-simulate-input.schema.json +1 -1
  71. package/schemas/probability-sweep-input.schema.json +1 -1
  72. package/schemas/reference-context-input.schema.json +1 -1
  73. package/schemas/reference-read-input.schema.json +1 -1
  74. package/schemas/reference-search-input.schema.json +1 -1
  75. package/schemas/scenario-suite.schema.json +1 -1
  76. package/schemas/scenario-test-input.schema.json +1 -1
  77. package/schemas/script-validate-input.schema.json +1 -1
  78. package/schemas/shared-scenario.schema.json +1 -1
  79. package/schemas/source-lookup-input.schema.json +1 -1
  80. package/server.json +2 -2
@@ -105,7 +105,7 @@ const mapRenderDataSchema = z
105
105
  const mapInspectOutputSchema = strictOperationResultSchema(z.union([mapWorkspaceInspectDataSchema, mapRenderDataSchema]));
106
106
  const mapPlanOutputSchema = strictOperationResultSchema(z
107
107
  .object({
108
- execution: z.enum(['applied', 'blocked', 'unchanged']),
108
+ execution: z.enum(['applied', 'blocked', 'unchanged', 'preview']),
109
109
  allocations: z.array(allocationEvidenceSchema).max(100),
110
110
  operationBlockers: z.array(operationBlockerDataSchema).max(100),
111
111
  expectedChangedBounds: changedBoundsSchema.nullable(),
@@ -116,6 +116,19 @@ const mapPlanOutputSchema = strictOperationResultSchema(z
116
116
  semanticDiffTruncated: z.boolean(),
117
117
  fileCount: nonNegativeIntegerSchema,
118
118
  artifactCount: nonNegativeIntegerSchema,
119
+ preview: z
120
+ .object({
121
+ offlineRepresentation: z.literal(true),
122
+ width: nonNegativeIntegerSchema,
123
+ height: nonNegativeIntegerSchema,
124
+ provinces: nonNegativeIntegerSchema,
125
+ states: nonNegativeIntegerSchema,
126
+ regions: nonNegativeIntegerSchema,
127
+ countries: nonNegativeIntegerSchema,
128
+ warningCount: nonNegativeIntegerSchema,
129
+ })
130
+ .strict()
131
+ .optional(),
119
132
  })
120
133
  .strict());
121
134
  const artifactProducing = {
@@ -136,7 +149,7 @@ export const mapTaskTools = [
136
149
  {
137
150
  name: 'hoi4.map_rewrite',
138
151
  title: 'Build a world or edit the map',
139
- description: 'Build a world, split or merge states, move or renumber provinces, and update script references. Regenerate building positions and supply networks. Example: {"operations":[{"id":"s1","kind":"create_state","provinceIds":[120,121],"displayName":"New State"}]}. Returns changed IDs, a summary of edits and a render.',
152
+ description: 'Build a world from a local reference image or edit provinces, states, regions and supply. Example: {"preview":true,"operations":[{"kind":"create_world","reference":{"imagePath":"references/world.png"}}]}. Preview first to check coastlines, terrain and provisional countries; omit preview to write the generated map and starting scenario. Reviewed masks and labels refine ambiguous artwork. Returns counts, diagnostics and linked PNGs. Offline validation does not prove game playability.',
140
153
  inputSchema: mapRewriteRequestSchema,
141
154
  outputSchema: mapPlanOutputSchema,
142
155
  annotations: {
@@ -143,6 +143,10 @@ export const mapRewriteRequestSchema = z
143
143
  .object({
144
144
  workspaceId: workspaceIdSchema,
145
145
  operations: z.array(compactMapOperationSchema).min(1).max(100),
146
+ preview: z
147
+ .boolean()
148
+ .default(false)
149
+ .describe('Return generated reference, terrain and political previews and counts without changing mod files.'),
146
150
  diffScale: z.number().int().min(1).max(16).optional(),
147
151
  requestKey: backgroundRequestKeySchema.optional(),
148
152
  })
@@ -1,7 +1,15 @@
1
1
  import { z } from 'zod/v4';
2
2
  import { renderDimensionViolation, RENDER_MAX_DIMENSION } from '../core/render-budget.js';
3
3
  import { MAP_MASK_CELL_LIMIT, MAP_SELECTED_PIXEL_LIMIT } from '../map/limits.js';
4
+ import { workspaceRelativePathSchema } from './common.js';
4
5
  const identifier = z.string().min(1).max(256);
6
+ // eslint-disable-next-line no-control-regex -- portable JSON Schema pattern rejects grammar-breaking controls.
7
+ const worldNamePattern = /^[^\u0000-\u001f\u007f]+$/u;
8
+ const worldName = z
9
+ .string()
10
+ .min(1)
11
+ .max(64)
12
+ .regex(worldNamePattern, 'World names must not contain control characters');
5
13
  const stateId = z.number().int().positive();
6
14
  const provinceId = z.number().int().min(0);
7
15
  const provinceTypeSchema = z.enum(['land', 'sea', 'lake']);
@@ -18,6 +26,83 @@ const operationBase = {
18
26
  id: identifier,
19
27
  summary: z.string().max(1000).optional(),
20
28
  };
29
+ const referencePointSchema = z
30
+ .object({ x: z.number().min(0).max(1), y: z.number().min(0).max(1) })
31
+ .strict();
32
+ const referenceKindSchema = z.enum([
33
+ 'sea',
34
+ 'land',
35
+ 'plains',
36
+ 'forest',
37
+ 'desert',
38
+ 'mountain',
39
+ 'snow',
40
+ 'lake',
41
+ 'volcano',
42
+ 'hills',
43
+ 'marsh',
44
+ 'jungle',
45
+ ]);
46
+ const referencePolygonSchema = z.array(referencePointSchema).min(3).max(1_000);
47
+ const referenceImagePathSchema = workspaceRelativePathSchema.refine((value) => !value.includes(':') && /\.(?:png|jpe?g|webp)$/iu.test(value), 'Reference must be a workspace-relative PNG, JPEG or WebP file');
48
+ /** Optional controls refine automatic interpretation; a single imagePath is sufficient. */
49
+ export const worldReferenceSchema = z
50
+ .object({
51
+ imagePath: referenceImagePathSchema,
52
+ politicalImagePath: referenceImagePathSchema.optional(),
53
+ terrainImagePath: referenceImagePathSchema.optional(),
54
+ heightImagePath: referenceImagePathSchema.optional(),
55
+ mode: z
56
+ .enum(['auto', 'illustrated', 'satellite', 'sketch', 'political', 'grayscale'])
57
+ .optional(),
58
+ crop: z
59
+ .object({
60
+ x: z.number().int().min(0),
61
+ y: z.number().int().min(0),
62
+ width: z.number().int().positive().max(65_536),
63
+ height: z.number().int().positive().max(65_536),
64
+ })
65
+ .strict()
66
+ .optional(),
67
+ ignoreMasks: z.array(referencePolygonSchema).max(64).optional(),
68
+ annotations: z
69
+ .array(z.object({ kind: referenceKindSchema, polygon: referencePolygonSchema }).strict())
70
+ .max(128)
71
+ .optional(),
72
+ riverAnnotations: z.array(z.array(referencePointSchema).min(2).max(1_000)).max(128).optional(),
73
+ palette: z
74
+ .array(z.object({ kind: referenceKindSchema, color: rgbSchema }).strict())
75
+ .max(64)
76
+ .optional(),
77
+ coastDetail: z.number().min(0).max(1).optional(),
78
+ labels: z
79
+ .array(z
80
+ .object({
81
+ name: worldName,
82
+ x: z.number().min(0).max(1),
83
+ y: z.number().min(0).max(1),
84
+ kind: z.enum(['state', 'country', 'city', 'sea', 'region', 'mountain', 'geography']),
85
+ })
86
+ .strict())
87
+ .max(2_048)
88
+ .optional(),
89
+ cities: z
90
+ .array(z
91
+ .object({
92
+ name: worldName,
93
+ x: z.number().min(0).max(1),
94
+ y: z.number().min(0).max(1),
95
+ countryTag: z
96
+ .string()
97
+ .regex(/^[A-Z][A-Z0-9]{2}$/u)
98
+ .optional(),
99
+ })
100
+ .strict())
101
+ .max(200)
102
+ .optional(),
103
+ })
104
+ .strict()
105
+ .describe('Generate from one local reference image. Optional controls use normalized 0..1 source coordinates; crop uses source pixels. Labels and political semantics may need refinement.');
21
106
  const moveStateDistributionSchema = z
22
107
  .object({
23
108
  stateValues: z.enum(['retain-in-current-states', 'proportional-by-land-pixels']),
@@ -473,8 +558,15 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
473
558
  .object({
474
559
  ...operationBase,
475
560
  kind: z.literal('create_world'),
476
- width: z.number().int().min(256).max(8_192).multipleOf(256).default(1_024),
477
- height: z.number().int().min(256).max(4_096).multipleOf(256).default(512),
561
+ worldName: worldName.optional(),
562
+ startingScenario: z
563
+ .boolean()
564
+ .optional()
565
+ .describe('Include basic starting units and stockpiles and replace old-world startup/AI geography. Defaults on for reference-image worlds.'),
566
+ id: identifier.default('world'),
567
+ reference: worldReferenceSchema.optional(),
568
+ width: z.number().int().min(256).max(8_192).multipleOf(256).optional(),
569
+ height: z.number().int().min(256).max(4_096).multipleOf(256).optional(),
478
570
  seed: z.number().int().min(0).max(2_147_483_647).default(1),
479
571
  landCoverage: z.number().min(0.05).max(0.9).default(0.35),
480
572
  continents: z.number().int().min(1).max(12).default(3),
@@ -485,28 +577,29 @@ export const mapOperationSchema = z.discriminatedUnion('kind', [
485
577
  .describe('Land outlines in top-left pixel coordinates; omit for generated continents.'),
486
578
  landProvincePixels: z.number().int().min(60).max(50_000).default(400),
487
579
  seaProvincePixels: z.number().int().min(200).max(200_000).default(2_500),
488
- provincesPerState: z.number().int().min(1).max(64).default(5),
489
- landProvincesPerRegion: z.number().int().min(2).max(400).default(25),
490
- seaProvincesPerRegion: z.number().int().min(1).max(200).default(10),
580
+ provincesPerState: z.number().int().min(1).max(64).optional(),
581
+ landProvincesPerRegion: z.number().int().min(2).max(400).optional(),
582
+ seaProvincesPerRegion: z.number().int().min(1).max(200).optional(),
491
583
  countries: z
492
584
  .array(z
493
585
  .object({
494
586
  tag: z.string().regex(/^[A-Z][A-Z0-9]{2}$/u),
495
- name: z.string().min(1).max(64),
496
- adjective: z.string().min(1).max(64).optional(),
587
+ name: worldName,
588
+ adjective: worldName.optional(),
497
589
  color: rgbSchema.optional(),
498
590
  share: z.number().min(0.05).max(100).optional(),
499
591
  ideology: z.enum(['democratic', 'communism', 'fascism', 'neutrality']).optional(),
500
592
  })
501
593
  .strict())
502
594
  .min(1)
503
- .max(200),
504
- continentNames: z.array(z.string().min(1).max(64)).max(12).optional(),
595
+ .max(200)
596
+ .optional(),
597
+ continentNames: z.array(worldName).max(12).optional(),
505
598
  startDate: z
506
599
  .string()
507
600
  .regex(/^\d{3,4}\.\d{1,2}\.\d{1,2}$/u)
508
601
  .default('1936.1.1'),
509
- rivers: z.number().int().min(0).max(500).default(8),
602
+ rivers: z.number().int().min(0).max(500).optional(),
510
603
  replaceBaseGame: z
511
604
  .boolean()
512
605
  .default(true)
@@ -1,8 +1,15 @@
1
1
  import { z } from 'zod/v4';
2
- import { TRANSACTION_MAX_APPLIED_FILES, TRANSACTION_MAX_ARTIFACTS, TRANSACTION_MAX_DIAGNOSTICS, TRANSACTION_MAX_FILES, TRANSACTION_MAX_OPERATIONS, TRANSACTION_MAX_READ_DEPENDENCIES, TRANSACTION_MAX_VALIDATION_CHECKS, } from '../core/transaction-limits.js';
2
+ import { TRANSACTION_MAX_APPLIED_FILES, TRANSACTION_MAX_ARTIFACTS, TRANSACTION_MAX_DIAGNOSTICS, TRANSACTION_MAX_FILES, TRANSACTION_MAX_OPERATIONS, TRANSACTION_MAX_PENDING_FILES, TRANSACTION_MAX_READ_DEPENDENCIES, TRANSACTION_MAX_VALIDATION_CHECKS, } from '../core/transaction-limits.js';
3
3
  import { TRANSACTION_VERSION } from '../version.js';
4
4
  import { diagnosticSchema, workspaceIdSchema, workspaceRelativePathSchema } from './common.js';
5
5
  const sha256Schema = z.string().regex(/^[a-f0-9]{64}$/u);
6
+ const compatibleTransactionVersion = z.union([z.literal(1), z.literal(TRANSACTION_VERSION)]);
7
+ const fileIdentitySchema = z
8
+ .object({
9
+ device: z.string().regex(/^(0|[1-9][0-9]{0,39})$/u),
10
+ inode: z.string().regex(/^(0|[1-9][0-9]{0,39})$/u),
11
+ })
12
+ .strict();
6
13
  const identifierSchema = z.string().min(1).max(256);
7
14
  const summarySchema = z.string().max(4096);
8
15
  const mediaTypeSchema = z.string().min(1).max(255);
@@ -55,7 +62,7 @@ const transactionFileSchema = z
55
62
  .strict();
56
63
  const transactionPlanPayloadSchema = z
57
64
  .object({
58
- version: z.literal(TRANSACTION_VERSION),
65
+ version: compatibleTransactionVersion,
59
66
  workspaceId: workspaceIdSchema,
60
67
  principal: z.string().max(1024).nullable(),
61
68
  rootFingerprint: sha256Schema,
@@ -72,7 +79,7 @@ const transactionPlanPayloadSchema = z
72
79
  .strict();
73
80
  export const transactionManifestSchema = z
74
81
  .object({
75
- version: z.literal(TRANSACTION_VERSION),
82
+ version: compatibleTransactionVersion,
76
83
  revision: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER),
77
84
  transactionId: z.string().regex(/^txn_[0-9a-f-]{36}$/u),
78
85
  workspaceId: workspaceIdSchema,
@@ -98,6 +105,16 @@ export const transactionManifestSchema = z
98
105
  artifacts: z.array(artifactLinkSchema).max(TRANSACTION_MAX_ARTIFACTS),
99
106
  executionArtifacts: z.array(artifactLinkSchema).max(TRANSACTION_MAX_ARTIFACTS).optional(),
100
107
  appliedFiles: z.array(workspaceRelativePathSchema).max(TRANSACTION_MAX_APPLIED_FILES),
108
+ pendingFiles: z
109
+ .array(z
110
+ .object({
111
+ relativePath: workspaceRelativePathSchema,
112
+ beforeIdentity: fileIdentitySchema.nullable(),
113
+ stagedIdentity: fileIdentitySchema.nullable(),
114
+ })
115
+ .strict())
116
+ .max(TRANSACTION_MAX_PENDING_FILES)
117
+ .optional(),
101
118
  rollbackStatus: z.enum(['available', 'applied', 'failed']),
102
119
  failure: z.object({ code: identifierSchema, message: summarySchema }).strict().optional(),
103
120
  })
@@ -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.0.5';
3
+ export const PACKAGE_VERSION = '4.1.0';
4
4
  export const MCP_PROTOCOL_VERSION = LATEST_PROTOCOL_VERSION;
5
5
  export const CONFIG_VERSION = 1;
6
- export const TRANSACTION_VERSION = 1;
6
+ export const TRANSACTION_VERSION = 2;
package/docs/map.md CHANGED
@@ -3,6 +3,51 @@
3
3
  Generate a new world, split states and reshape provinces. Preview the map and update the scripts that refer to it.
4
4
  Use `hoi4.map_inspect` and `hoi4.map_rewrite` for provinces, states, regions, supply, railways, positions, ownership, resources, buildings and localisation.
5
5
 
6
+ ## Generate a world from one image
7
+
8
+ Put a PNG, JPEG or WebP reference in your mod workspace, then call `hoi4.map_rewrite`:
9
+
10
+ ```json
11
+ {
12
+ "operations": [{ "kind": "create_world", "reference": { "imagePath": "references/sketch.png" } }]
13
+ }
14
+ ```
15
+
16
+ For a first run, copy the project-owned [synthetic hand sketch](images/reference-maps/sketch.png) to `references/sketch.png` in your mod. The [synthetic flat political map](images/reference-maps/political.png) is another ready-made input; copy it to `references/political.png` and change `imagePath` accordingly. The examples are intentionally small and contain no third-party artwork.
17
+
18
+ The server generates the complete map and supporting country/state files in one validated atomic rewrite. No country list, manual masks, coordinates or separate apply call is required. `workspaceId` defaults to the current mod, and the operation ID defaults to `world`. The default reference world is 5632 by 2048 pixels; the artwork retains its aspect ratio within sea padding. Set `width` and `height` to choose other multiples of 256 within the map pixel budget. Reference defaults group 12 provinces per state, 60 land provinces or 30 sea provinces per strategic region, and request 80 rivers; explicit values override these defaults.
19
+
20
+ Add `"preview": true` at the top level to return offline reference, terrain and political PNGs, a comparison and counts without changing mod files. Repeat the request without `preview` to apply it. Preview uses the same extraction, generation and validation as application, and never launches the game.
21
+
22
+ Use a cleaned, unlabelled raster for production generation. Handwriting, sheet joins and map frames can become false islands or relief. Keep the original unchanged, remove these marks in a separate reference, compare its coastlines with the original, then inspect the terrain preview before writing. Supply reviewed names through `reference.labels` and `reference.cities`; an explicit label list, including an empty list, prevents OCR from adding unreviewed names. The server does not automatically redraw artwork or promise lossless interpretation of every map.
23
+
24
+ Reference worlds enable `startingScenario` by default; procedural worlds keep it off unless requested. The starter includes a bookmark, country histories, provisional councils, recruitable infantry templates, two equipped divisions per country, stockpiles, an infantry production line, supply and building positions. `worldName` sets the bookmark title. Demographics, industry and ownership remain provisional. Existing world events, decisions, startup hooks and country-specific AI strategy folders are replaced; shared game systems remain dependencies and can report unused-definition warnings. Generated names use the localisation replacement folder, and explicit country color declarations match the flags. Inherited country tags receive inactive histories. The adjacent launcher `.mod` file must mirror the generated `descriptor.mod` replacement paths. The server cannot enable a launcher playset or test a campaign.
25
+
26
+ Regeneration records generated paths and hashes in `.hoi4-agent-world.json`. It removes obsolete recorded outputs through the same atomic rewrite and refuses to overwrite recorded files edited since generation. Preserve that inventory. Unrelated authored files are not deleted; validation can block a rebuild if they conflict with the generated world. Large world diffs remain linked evidence so counts and preview images fit within the MCP response budget.
27
+
28
+ Automatic interpretation recognizes visual land/water and terrain cues, derives relief and drainage, and uses flat political colors when available. Offline English OCR supplies candidate names and city locations; recognized text bounds are masked before terrain classification so ocean labels do not become islands. Masks near a coastline can soften its detail and produce a review warning. Decorative text, handwriting, unknown languages and visually ambiguous borders may be missed or misclassified; diagnostics identify uncertain interpretation and deterministic defaults. Population, resources, buildings and country ownership inferred from artwork are generated assumptions, not historical facts or a balance assessment.
29
+
30
+ Optional `reference` controls refine the same call:
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. |
44
+
45
+ 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
+
47
+ 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
+
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.
50
+
6
51
  ## Inspect and navigate the complete map
7
52
 
8
53
  `hoi4.map_inspect` finds map problems and returns a full-map PNG plus a searchable, clickable HTML navigator by default. The navigator supports pan, zoom, fit-to-map, search by numeric ID, localisation key, or localised name, and exact click lookup for province, state, and strategic-region IDs. Hovering reports both top-left bitmap coordinates and bottom-left HOI4 map coordinates.
@@ -8,4 +8,10 @@ Copyright 2011 The Roboto Project Authors (<https://github.com/googlefonts/robot
8
8
 
9
9
  Roboto is licensed under the SIL Open Font License, Version 1.1 (`OFL-1.1`). The complete license text is distributed as `LICENSE` in the installed `@fontsource-variable/roboto` package. The font package is pinned in `package.json` and the published `npm-shrinkwrap.json`; generated SVG/PNG documents may contain derived glyph outlines or pixels as permitted by the OFL.
10
10
 
11
+ ## Offline reference-image OCR
12
+
13
+ `tesseract.js` version 7.0.0 and its `tesseract.js-core` dependency provide the local WebAssembly OCR engine under Apache-2.0. English trained data is installed through `@tesseract.js-data/eng` version 1.0.0. That package declares MIT licensing for its packaging; its upstream [tessdata repository](https://github.com/naptha/tessdata) distributes Tesseract language data under [Apache-2.0](https://github.com/naptha/tessdata/blob/gh-pages/LICENSE). The project uses the unmodified `4.0.0_best_int/eng.traineddata.gz` model from the dependency. No reference artwork or language model is copied into project fixtures or source assets.
14
+
15
+ These dependencies are pinned by `package.json` and `npm-shrinkwrap.json`. OCR uses the installed worker, engine and language bytes without runtime network downloads or external OCR executables. The engine and language data retain their upstream licenses.
16
+
11
17
  The application source remains licensed under Apache-2.0 as stated in the repository [LICENSE](../LICENSE). Third-party components retain their respective licenses.
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "hoi4-agent-tools",
3
- "version": "4.0.5",
3
+ "version": "4.1.0",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "hoi4-agent-tools",
9
- "version": "4.0.5",
9
+ "version": "4.1.0",
10
10
  "license": "Apache-2.0",
11
11
  "dependencies": {
12
12
  "@fontsource-variable/roboto": "5.3.0",
@@ -14,12 +14,14 @@
14
14
  "@modelcontextprotocol/node": "2.1.1",
15
15
  "@modelcontextprotocol/sdk": "1.32.1",
16
16
  "@modelcontextprotocol/server": "2.3.1",
17
+ "@tesseract.js-data/eng": "1.0.0",
17
18
  "express": "5.2.1",
18
19
  "fontkit": "2.0.4",
19
20
  "glob": "13.0.6",
20
21
  "iconv-lite": "0.7.3",
21
22
  "jose": "6.2.8",
22
23
  "sharp": "0.35.5",
24
+ "tesseract.js": "7.0.0",
23
25
  "zod": "4.4.3"
24
26
  },
25
27
  "bin": {
@@ -2369,6 +2371,12 @@
2369
2371
  "tslib": "^2.8.0"
2370
2372
  }
2371
2373
  },
2374
+ "node_modules/@tesseract.js-data/eng": {
2375
+ "version": "1.0.0",
2376
+ "resolved": "https://registry.npmjs.org/@tesseract.js-data/eng/-/eng-1.0.0.tgz",
2377
+ "integrity": "sha512-mbTumm6KQPUHyzTPQaF3ObXYnx0SqqfV2nabqFVQBwD6Kl7PhGSLSzOlfFTWy0P3BjghaSKA2W9GB19Jk+ZcTg==",
2378
+ "license": "MIT"
2379
+ },
2372
2380
  "node_modules/@types/body-parser": {
2373
2381
  "version": "1.19.6",
2374
2382
  "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz",
@@ -3132,6 +3140,12 @@
3132
3140
  ],
3133
3141
  "license": "MIT"
3134
3142
  },
3143
+ "node_modules/bmp-js": {
3144
+ "version": "0.1.0",
3145
+ "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
3146
+ "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==",
3147
+ "license": "MIT"
3148
+ },
3135
3149
  "node_modules/body-parser": {
3136
3150
  "version": "2.3.0",
3137
3151
  "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz",
@@ -4431,6 +4445,12 @@
4431
4445
  "url": "https://opencollective.com/express"
4432
4446
  }
4433
4447
  },
4448
+ "node_modules/idb-keyval": {
4449
+ "version": "6.3.0",
4450
+ "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.3.0.tgz",
4451
+ "integrity": "sha512-um+2dgAWmYsu615EXpWVwSmapJhON0G43t3Ka/EVaohzPQXSMqKEqeDK/oIW3Ow+BXaF2PvSc+oBTFp793A5Ow==",
4452
+ "license": "Apache-2.0"
4453
+ },
4434
4454
  "node_modules/ignore": {
4435
4455
  "version": "5.3.2",
4436
4456
  "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4743,6 +4763,12 @@
4743
4763
  "tslib": "^2.0.3"
4744
4764
  }
4745
4765
  },
4766
+ "node_modules/is-url": {
4767
+ "version": "1.2.4",
4768
+ "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz",
4769
+ "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==",
4770
+ "license": "MIT"
4771
+ },
4746
4772
  "node_modules/is-wsl": {
4747
4773
  "version": "3.1.1",
4748
4774
  "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-3.1.1.tgz",
@@ -5359,6 +5385,26 @@
5359
5385
  "node": ">= 0.6"
5360
5386
  }
5361
5387
  },
5388
+ "node_modules/node-fetch": {
5389
+ "version": "2.7.0",
5390
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
5391
+ "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
5392
+ "license": "MIT",
5393
+ "dependencies": {
5394
+ "whatwg-url": "^5.0.0"
5395
+ },
5396
+ "engines": {
5397
+ "node": "4.x || >=6.0.0"
5398
+ },
5399
+ "peerDependencies": {
5400
+ "encoding": "^0.1.0"
5401
+ },
5402
+ "peerDependenciesMeta": {
5403
+ "encoding": {
5404
+ "optional": true
5405
+ }
5406
+ }
5407
+ },
5362
5408
  "node_modules/object-assign": {
5363
5409
  "version": "4.1.1",
5364
5410
  "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@@ -5460,6 +5506,15 @@
5460
5506
  "url": "https://github.com/sponsors/sindresorhus"
5461
5507
  }
5462
5508
  },
5509
+ "node_modules/opencollective-postinstall": {
5510
+ "version": "2.0.3",
5511
+ "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz",
5512
+ "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==",
5513
+ "license": "MIT",
5514
+ "bin": {
5515
+ "opencollective-postinstall": "index.js"
5516
+ }
5517
+ },
5463
5518
  "node_modules/optionator": {
5464
5519
  "version": "0.9.4",
5465
5520
  "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -5888,6 +5943,12 @@
5888
5943
  "node": ">= 12.13.0"
5889
5944
  }
5890
5945
  },
5946
+ "node_modules/regenerator-runtime": {
5947
+ "version": "0.13.11",
5948
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
5949
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
5950
+ "license": "MIT"
5951
+ },
5891
5952
  "node_modules/require-from-string": {
5892
5953
  "version": "2.0.2",
5893
5954
  "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
@@ -6410,6 +6471,30 @@
6410
6471
  "url": "https://github.com/sponsors/sindresorhus"
6411
6472
  }
6412
6473
  },
6474
+ "node_modules/tesseract.js": {
6475
+ "version": "7.0.0",
6476
+ "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-7.0.0.tgz",
6477
+ "integrity": "sha512-exPBkd+z+wM1BuMkx/Bjv43OeLBxhL5kKWsz/9JY+DXcXdiBjiAch0V49QR3oAJqCaL5qURE0vx9Eo+G5YE7mA==",
6478
+ "hasInstallScript": true,
6479
+ "license": "Apache-2.0",
6480
+ "dependencies": {
6481
+ "bmp-js": "^0.1.0",
6482
+ "idb-keyval": "^6.2.0",
6483
+ "is-url": "^1.2.4",
6484
+ "node-fetch": "^2.6.9",
6485
+ "opencollective-postinstall": "^2.0.3",
6486
+ "regenerator-runtime": "^0.13.3",
6487
+ "tesseract.js-core": "^7.0.0",
6488
+ "wasm-feature-detect": "^1.8.0",
6489
+ "zlibjs": "^0.3.1"
6490
+ }
6491
+ },
6492
+ "node_modules/tesseract.js-core": {
6493
+ "version": "7.0.0",
6494
+ "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-7.0.0.tgz",
6495
+ "integrity": "sha512-WnNH518NzmbSq9zgTPeoF8c+xmilS8rFIl1YKbk/ptuuc7p6cLNELNuPAzcmsYw450ca6bLa8j3t0VAtq435Vw==",
6496
+ "license": "Apache-2.0"
6497
+ },
6413
6498
  "node_modules/thread-stream": {
6414
6499
  "version": "3.2.0",
6415
6500
  "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz",
@@ -6511,6 +6596,12 @@
6511
6596
  "node": ">=0.6"
6512
6597
  }
6513
6598
  },
6599
+ "node_modules/tr46": {
6600
+ "version": "0.0.3",
6601
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
6602
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
6603
+ "license": "MIT"
6604
+ },
6514
6605
  "node_modules/ts-api-utils": {
6515
6606
  "version": "2.5.0",
6516
6607
  "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
@@ -6907,6 +6998,28 @@
6907
6998
  "url": "https://github.com/sponsors/jonschlinkert"
6908
6999
  }
6909
7000
  },
7001
+ "node_modules/wasm-feature-detect": {
7002
+ "version": "1.9.0",
7003
+ "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.9.0.tgz",
7004
+ "integrity": "sha512-zonE+xlIIYtxPy++L24ow0hAD8CICb4+FgPyROd3buyXIqsJvUEDkBgfCCoXOd1Hu3DUr0GOfnPIdcGV+YpNaA==",
7005
+ "license": "Apache-2.0"
7006
+ },
7007
+ "node_modules/webidl-conversions": {
7008
+ "version": "3.0.1",
7009
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
7010
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
7011
+ "license": "BSD-2-Clause"
7012
+ },
7013
+ "node_modules/whatwg-url": {
7014
+ "version": "5.0.0",
7015
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
7016
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
7017
+ "license": "MIT",
7018
+ "dependencies": {
7019
+ "tr46": "~0.0.3",
7020
+ "webidl-conversions": "^3.0.0"
7021
+ }
7022
+ },
6910
7023
  "node_modules/when-exit": {
6911
7024
  "version": "2.1.5",
6912
7025
  "resolved": "https://registry.npmjs.org/when-exit/-/when-exit-2.1.5.tgz",
@@ -7077,6 +7190,15 @@
7077
7190
  "dev": true,
7078
7191
  "license": "MIT"
7079
7192
  },
7193
+ "node_modules/zlibjs": {
7194
+ "version": "0.3.1",
7195
+ "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz",
7196
+ "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==",
7197
+ "license": "MIT",
7198
+ "engines": {
7199
+ "node": "*"
7200
+ }
7201
+ },
7080
7202
  "node_modules/zod": {
7081
7203
  "version": "4.4.3",
7082
7204
  "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz",
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.0.5",
4
+ "version": "4.1.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",
@@ -52,6 +52,7 @@
52
52
  "docs/focus.md",
53
53
  "docs/gui.md",
54
54
  "docs/map.md",
55
+ "docs/images/reference-maps/",
55
56
  "docs/reference.md",
56
57
  "docs/research/reference-benchmark.md",
57
58
  "docs/http.md",
@@ -119,12 +120,14 @@
119
120
  "@modelcontextprotocol/node": "2.1.1",
120
121
  "@modelcontextprotocol/sdk": "1.32.1",
121
122
  "@modelcontextprotocol/server": "2.3.1",
123
+ "@tesseract.js-data/eng": "1.0.0",
122
124
  "express": "5.2.1",
123
125
  "fontkit": "2.0.4",
124
126
  "glob": "13.0.6",
125
127
  "iconv-lite": "0.7.3",
126
128
  "jose": "6.2.8",
127
129
  "sharp": "0.35.5",
130
+ "tesseract.js": "7.0.0",
128
131
  "zod": "4.4.3"
129
132
  },
130
133
  "devDependencies": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.0.5/schemas/configuration.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.1.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.0.5/schemas/continuous-focus-palette.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.1.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.0.5/schemas/custom-weighted-pool.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.1.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.0.5/schemas/decision-inspect-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.1.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.0.5/schemas/error-log-input.schema.json",
2
+ "$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v4.1.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": {