hoi4-agent-tools 3.4.1 → 3.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 (96) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +26 -18
  3. package/dist/hoi4_agent_tools/core/condition-evaluator.js +41 -2
  4. package/dist/hoi4_agent_tools/core/condition-schema.js +18 -0
  5. package/dist/hoi4_agent_tools/core/configuration.js +10 -0
  6. package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +9 -1
  7. package/dist/hoi4_agent_tools/core/impact-semantic.js +11 -4
  8. package/dist/hoi4_agent_tools/core/scanner.js +1 -1
  9. package/dist/hoi4_agent_tools/core/scenario-model.js +6 -0
  10. package/dist/hoi4_agent_tools/core/scenario-state.js +3 -0
  11. package/dist/hoi4_agent_tools/core/workspace.js +13 -1
  12. package/dist/hoi4_agent_tools/event/graph.js +8 -1
  13. package/dist/hoi4_agent_tools/event/service.js +1 -0
  14. package/dist/hoi4_agent_tools/event/source-analysis.js +1 -1
  15. package/dist/hoi4_agent_tools/focus/render.js +10 -2
  16. package/dist/hoi4_agent_tools/gui/layout.js +58 -11
  17. package/dist/hoi4_agent_tools/gui/scenario.js +3 -1
  18. package/dist/hoi4_agent_tools/gui/source-graph.js +4 -1
  19. package/dist/hoi4_agent_tools/gui/studio.js +32 -10
  20. package/dist/hoi4_agent_tools/map/model.js +121 -12
  21. package/dist/hoi4_agent_tools/map/operations.js +80 -7
  22. package/dist/hoi4_agent_tools/map/service.js +1 -0
  23. package/dist/hoi4_agent_tools/map/validation.js +38 -1
  24. package/dist/hoi4_agent_tools/mcp/server/create-modern-operations.js +18 -1
  25. package/dist/hoi4_agent_tools/mcp/server/create.js +2 -0
  26. package/dist/hoi4_agent_tools/mcp/server/instructions.js +1 -1
  27. package/dist/hoi4_agent_tools/mcp/tools/reference.js +60 -0
  28. package/dist/hoi4_agent_tools/probability/service.js +2 -2
  29. package/dist/hoi4_agent_tools/probability/source-analysis.js +16 -0
  30. package/dist/hoi4_agent_tools/probability/value-evaluator.js +2 -1
  31. package/dist/hoi4_agent_tools/reference/lines.js +52 -0
  32. package/dist/hoi4_agent_tools/reference/operations.js +41 -0
  33. package/dist/hoi4_agent_tools/reference/service.js +427 -0
  34. package/dist/hoi4_agent_tools/reference/source-lookup.js +90 -0
  35. package/dist/hoi4_agent_tools/schemas/analysis.js +2 -0
  36. package/dist/hoi4_agent_tools/schemas/gui-requests.js +1 -1
  37. package/dist/hoi4_agent_tools/schemas/probability-requests.js +1 -1
  38. package/dist/hoi4_agent_tools/schemas/probability.js +26 -2
  39. package/dist/hoi4_agent_tools/schemas/reference.js +160 -0
  40. package/dist/hoi4_agent_tools/technology/render.js +9 -4
  41. package/dist/hoi4_agent_tools/version.js +1 -1
  42. package/docs/README.md +3 -0
  43. package/docs/gui.md +6 -0
  44. package/docs/map.md +8 -0
  45. package/docs/probability.md +7 -0
  46. package/docs/reference.md +32 -0
  47. package/docs/research/reference-benchmark.md +28 -0
  48. package/docs/setup.md +6 -0
  49. package/examples/clients/codex-config.toml +2 -0
  50. package/package.json +4 -2
  51. package/schemas/configuration.schema.json +72 -58
  52. package/schemas/continuous-focus-palette.schema.json +1 -1
  53. package/schemas/custom-weighted-pool.schema.json +1 -1
  54. package/schemas/decision-inspect-input.schema.json +43 -26
  55. package/schemas/event-feature-manifest.schema.json +1 -1
  56. package/schemas/focus-plan.schema.json +1 -1
  57. package/schemas/focus-planning-sidecar.schema.json +1 -1
  58. package/schemas/gui-animation-source.schema.json +1 -1
  59. package/schemas/gui-helper.schema.json +1 -1
  60. package/schemas/gui-scenario.schema.json +102 -75
  61. package/schemas/helper-expansion-request.schema.json +1 -1
  62. package/schemas/helper-expansion-summary.schema.json +1 -1
  63. package/schemas/impact-inspect-input.schema.json +1 -1
  64. package/schemas/map-operation.schema.json +1 -1
  65. package/schemas/mechanic-test-input.schema.json +49 -32
  66. package/schemas/operation-result.schema.json +1 -1
  67. package/schemas/package-check-input.schema.json +1 -1
  68. package/schemas/package-manifest.schema.json +1 -1
  69. package/schemas/probability-analysis-result.schema.json +1 -1
  70. package/schemas/probability-compare-input.schema.json +160 -131
  71. package/schemas/probability-evaluate-input.schema.json +161 -132
  72. package/schemas/probability-inspect-input.schema.json +20 -11
  73. package/schemas/probability-render-input.schema.json +1 -1
  74. package/schemas/probability-scenario-set.schema.json +110 -90
  75. package/schemas/probability-sequence-input.schema.json +154 -134
  76. package/schemas/probability-simulate-input.schema.json +161 -132
  77. package/schemas/probability-sweep-input.schema.json +163 -134
  78. package/schemas/reference-context-input.schema.json +42 -0
  79. package/schemas/reference-read-input.schema.json +41 -0
  80. package/schemas/reference-search-input.schema.json +45 -0
  81. package/schemas/scenario-suite.schema.json +92 -75
  82. package/schemas/scenario-test-input.schema.json +103 -86
  83. package/schemas/shared-scenario.schema.json +39 -22
  84. package/schemas/source-lookup-input.schema.json +61 -0
  85. package/server.json +2 -2
  86. package/docs/images/readme/chaos-meter-ingame.png +0 -0
  87. package/docs/images/readme/chaos-meter-mcp.png +0 -0
  88. package/docs/images/readme/communist-insurgency-ingame.png +0 -0
  89. package/docs/images/readme/communist-insurgency-mcp.png +0 -0
  90. package/docs/images/readme/event-chain-mcp.png +0 -0
  91. package/docs/images/readme/focus-tree-mcp.png +0 -0
  92. package/docs/images/readme/map-mcp.png +0 -0
  93. package/docs/images/readme/probability-mcp.png +0 -0
  94. package/docs/images/readme/settings-advanced-mcp.png +0 -0
  95. package/docs/images/readme/settings-events-mcp.png +0 -0
  96. package/docs/images/readme/technology-tree-mcp.png +0 -0
@@ -4,7 +4,7 @@ import { workspaceIdSchema } from './common.js';
4
4
  function compact(schema, description) {
5
5
  return z.unknown().describe(description).pipe(schema);
6
6
  }
7
- const nestedSource = compact(probabilitySourceSchema, 'Source selector or proposed source.');
7
+ const nestedSource = compact(probabilitySourceSchema, 'Source selector; snapshotPath binds frozen bytes to path with expectedSourceHash.');
8
8
  const nestedScenarios = compact(probabilityScenarioSetSchema, 'Explicit world-state scenarios.');
9
9
  const nestedManifest = compact(customWeightedPoolManifestSchema, 'Declared custom weighted pool and state transitions.');
10
10
  const nestedRenderFilter = compact(probabilityRenderFilterSchema, 'Optional scenario, candidate, metric, diagnostic, source, and page filter.');
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod/v4';
2
- import { diagnosticSchema, sourceLocationSchema, workspaceIdSchema } from './common.js';
2
+ import { diagnosticSchema, sourceLocationSchema, workspaceIdSchema, workspaceRelativePathSchema, } from './common.js';
3
3
  import { artifactLinkSchema } from './transaction.js';
4
+ import { ConditionControlMapSchema } from '../core/condition-schema.js';
4
5
  export const probabilityAdapterIdSchema = z.enum([
5
6
  'event_mean_time_to_happen',
6
7
  'event_option_ai_chance',
@@ -145,6 +146,7 @@ export const probabilityScenarioSchema = z
145
146
  prevalence: z.number().min(0).max(1).optional(),
146
147
  actor: z.string().max(256).optional(),
147
148
  date: z.string().max(64).optional(),
149
+ controls: ConditionControlMapSchema.optional(),
148
150
  state: z.record(z.string().min(1).max(1024), scenarioValueSchema),
149
151
  flags: z.array(z.string().min(1).max(512)).max(100_000).optional(),
150
152
  eventTargets: z.record(z.string().max(512), z.string().max(512)).optional(),
@@ -257,10 +259,12 @@ export const probabilityDiagnosticThresholdsSchema = z
257
259
  })
258
260
  .strict();
259
261
  export const probabilitySamplingMethodSchema = z.enum(['latin_hypercube', 'pseudo_random']);
262
+ const probabilitySourcePathSchema = workspaceRelativePathSchema.refine((value) => !/[:*?[\]{}!]/u.test(value), 'Source path must be an exact workspace-relative file path');
260
263
  export const probabilitySourceSchema = z
261
264
  .object({
262
265
  identifier: z.string().min(1).max(1024).optional(),
263
- path: z.string().min(1).max(1024).optional(),
266
+ path: probabilitySourcePathSchema.optional(),
267
+ snapshotPath: probabilitySourcePathSchema.optional(),
264
268
  line: z.number().int().min(1).optional(),
265
269
  inlineClausewitz: z.string().max(16_777_216).optional(),
266
270
  virtualPatch: z.string().max(16_777_216).optional(),
@@ -270,6 +274,26 @@ export const probabilitySourceSchema = z
270
274
  .optional(),
271
275
  })
272
276
  .strict()
277
+ .superRefine((value, context) => {
278
+ if (value.snapshotPath === undefined)
279
+ return;
280
+ if (value.path === undefined || value.expectedSourceHash === undefined)
281
+ context.addIssue({
282
+ code: 'custom',
283
+ message: 'snapshotPath requires the logical source path and expectedSourceHash of the frozen bytes',
284
+ });
285
+ if (value.inlineClausewitz !== undefined || value.virtualPatch !== undefined)
286
+ context.addIssue({
287
+ code: 'custom',
288
+ message: 'snapshotPath cannot be combined with inline source or a virtual patch',
289
+ });
290
+ if (value.path !== undefined && !workspaceRelativePathSchema.safeParse(value.path).success)
291
+ context.addIssue({
292
+ code: 'custom',
293
+ path: ['path'],
294
+ message: 'Snapshot logical source path must be workspace-relative',
295
+ });
296
+ })
273
297
  .refine((value) => value.identifier !== undefined ||
274
298
  value.path !== undefined ||
275
299
  value.inlineClausewitz !== undefined ||
@@ -0,0 +1,160 @@
1
+ import { z } from 'zod/v4';
2
+ import { workspaceIdSchema } from './common.js';
3
+ export const referenceSourceSchema = z.enum(['game_doc', 'wiki', 'script_doc']);
4
+ export const referenceSurfaceSchema = z.enum([
5
+ 'general',
6
+ 'event',
7
+ 'decision',
8
+ 'idea',
9
+ 'focus',
10
+ 'technology',
11
+ 'gui',
12
+ 'map',
13
+ 'localisation',
14
+ 'ai',
15
+ ]);
16
+ export const referenceSearchRequestSchema = z
17
+ .object({
18
+ workspaceId: workspaceIdSchema,
19
+ query: z.string().trim().min(2).max(160),
20
+ sources: z.array(referenceSourceSchema).min(1).max(3).optional(),
21
+ limit: z.number().int().min(1).max(12).default(6),
22
+ })
23
+ .strict();
24
+ export const referenceReadRequestSchema = z
25
+ .object({
26
+ workspaceId: workspaceIdSchema,
27
+ id: z.string().regex(/^[a-f0-9]{64}$/u),
28
+ revision: z.string().regex(/^[a-f0-9]{64}$/u),
29
+ startLine: z.number().int().min(1).optional(),
30
+ startColumn: z.number().int().min(1).optional(),
31
+ maxLines: z.number().int().min(1).max(80).default(40),
32
+ })
33
+ .strict();
34
+ export const referenceContextRequestSchema = z
35
+ .object({
36
+ workspaceId: workspaceIdSchema,
37
+ surface: referenceSurfaceSchema,
38
+ question: z.string().trim().min(2).max(160).optional(),
39
+ limit: z.number().int().min(1).max(24).default(16),
40
+ })
41
+ .strict();
42
+ export const sourceLookupRequestSchema = z
43
+ .object({
44
+ workspaceId: workspaceIdSchema,
45
+ symbol: z.string().trim().min(1).max(256),
46
+ kind: z
47
+ .string()
48
+ .regex(/^[a-z][a-z0-9_]{0,63}$/u)
49
+ .optional(),
50
+ includeReferences: z.boolean().default(true),
51
+ maxDefinitions: z.number().int().min(1).max(3).default(3),
52
+ maxReferences: z.number().int().min(0).max(20).default(10),
53
+ maxLines: z.number().int().min(1).max(80).default(30),
54
+ fromLine: z.number().int().min(1).optional(),
55
+ fromColumn: z.number().int().min(1).optional(),
56
+ expectedRevision: z
57
+ .string()
58
+ .regex(/^[a-f0-9]{64}$/u)
59
+ .optional(),
60
+ })
61
+ .strict();
62
+ const hash = z.string().regex(/^[a-f0-9]{64}$/u);
63
+ const line = z.number().int().min(1);
64
+ const count = z.number().int().min(0);
65
+ const coverage = z
66
+ .object({
67
+ files: count,
68
+ sections: count,
69
+ unavailable: z.boolean(),
70
+ })
71
+ .strict();
72
+ const coverageBySource = z
73
+ .object({ game_doc: coverage, wiki: coverage, script_doc: coverage })
74
+ .strict();
75
+ export const referenceSectionSchema = z
76
+ .object({
77
+ id: hash,
78
+ revision: hash,
79
+ source: referenceSourceSchema,
80
+ title: z.string().max(512),
81
+ heading: z.string().max(512),
82
+ path: z.string().max(4096),
83
+ startLine: line,
84
+ endLine: line,
85
+ excerpt: z.string().max(300),
86
+ authority: z.string().max(80),
87
+ })
88
+ .strict();
89
+ export const referenceSearchDataSchema = z
90
+ .object({
91
+ results: z.array(referenceSectionSchema.extend({ score: z.number() })).max(12),
92
+ total: count,
93
+ coverage: coverageBySource,
94
+ skipped: count,
95
+ })
96
+ .strict();
97
+ export const referenceReadDataSchema = referenceSectionSchema
98
+ .extend({
99
+ sectionStartLine: line,
100
+ sectionEndLine: line,
101
+ startColumn: line,
102
+ text: z.string().max(8000),
103
+ nextLine: line.nullable(),
104
+ nextColumn: line.nullable(),
105
+ })
106
+ .strict();
107
+ export const referenceContextDataSchema = z
108
+ .object({
109
+ surface: referenceSurfaceSchema,
110
+ sections: z.array(referenceSectionSchema).max(24),
111
+ omitted: count,
112
+ missing: z.array(z.string().max(256)).max(32),
113
+ coverage: coverageBySource,
114
+ skipped: count,
115
+ })
116
+ .strict();
117
+ export const sourceLookupDataSchema = z
118
+ .object({
119
+ revision: hash,
120
+ complete: z.boolean(),
121
+ skippedSourceCount: count,
122
+ definitionCount: count,
123
+ referencesIncluded: z.boolean(),
124
+ referenceCount: count,
125
+ referencesTruncated: z.boolean(),
126
+ referencesComplete: z.boolean(),
127
+ unresolvedReferenceCount: count,
128
+ definitions: z
129
+ .array(z
130
+ .object({
131
+ kind: z.string().max(64),
132
+ id: z.string().max(1024),
133
+ path: z.string().max(4096),
134
+ rootKind: z.string().max(32),
135
+ loadOrder: count,
136
+ overridden: z.boolean(),
137
+ sourceShadowed: z.boolean(),
138
+ startLine: line.nullable(),
139
+ endLine: line.nullable(),
140
+ fromLine: line.nullable(),
141
+ toLine: line.nullable(),
142
+ nextLine: line.nullable(),
143
+ nextColumn: line.nullable(),
144
+ text: z.string().max(2000),
145
+ })
146
+ .strict())
147
+ .max(3),
148
+ references: z
149
+ .array(z
150
+ .object({
151
+ kind: z.string().max(64),
152
+ from: z.string().max(1024),
153
+ toKind: z.string().max(64),
154
+ path: z.string().max(4096),
155
+ line: line.nullable(),
156
+ })
157
+ .strict())
158
+ .max(20),
159
+ })
160
+ .strict();
@@ -744,10 +744,15 @@ function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris, spri
744
744
  ? undefined
745
745
  : (iconDataUris[preferredSprite] ?? iconDataUris[node.itemBackgroundSprite]);
746
746
  const icon = node.iconSprite === undefined ? undefined : iconDataUris[node.iconSprite];
747
- const iconSize = node.layoutSize === 'small' ? 62 : 70;
747
+ const nativeIconSize = node.layoutSize === 'large' && node.iconSprite !== undefined
748
+ ? spriteDimensions[node.iconSprite]
749
+ : undefined;
750
+ const fallbackIconSize = node.layoutSize === 'small' ? 62 : 70;
751
+ const iconWidth = nativeIconSize?.width ?? fallbackIconSize;
752
+ const iconHeight = nativeIconSize?.height ?? fallbackIconSize;
748
753
  const center = node.iconPosition ?? { x: node.width / 2, y: node.height / 2, centered: true };
749
- const iconX = node.x + center.x - (center.centered ? iconSize / 2 : 0);
750
- const iconY = node.y + center.y - (center.centered ? iconSize / 2 : 0);
754
+ const iconX = node.x + center.x - (center.centered ? iconWidth / 2 : 0);
755
+ const iconY = node.y + center.y - (center.centered ? iconHeight / 2 : 0);
751
756
  const name = node.namePosition ?? { x: 3, y: -3, maxWidth: node.width - 6 };
752
757
  const labelWidth = Math.min(name.maxWidth ?? node.width - 6, node.width - name.x - 3);
753
758
  let fittedLabel = node.label;
@@ -767,7 +772,7 @@ function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris, spri
767
772
  : `<image data-item-background-sprite="${escapeXml(preferredSprite)}" href="${escapeXml(itemSkin)}" x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" preserveAspectRatio="none"/>`;
768
773
  const iconSvg = icon === undefined
769
774
  ? ''
770
- : `<image href="${escapeXml(icon)}" x="${iconX}" y="${iconY}" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet"/>`;
775
+ : `<image data-tech-icon-sprite="${escapeXml(node.iconSprite)}" href="${escapeXml(icon)}" x="${iconX}" y="${iconY}" width="${iconWidth}" height="${iconHeight}" preserveAspectRatio="xMidYMid meet"/>`;
771
776
  const designTeamSprite = node.designTeamIcon?.sprite;
772
777
  const designTeamImage = designTeamSprite === undefined ? undefined : iconDataUris[designTeamSprite];
773
778
  const designTeamSize = designTeamSprite === undefined ? undefined : spriteDimensions[designTeamSprite];
@@ -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 = '3.4.1';
3
+ export const PACKAGE_VERSION = '3.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 = 1;
package/docs/README.md CHANGED
@@ -1,10 +1,13 @@
1
1
  # Documentation
2
2
 
3
3
  - [Setup](setup.md): install the server, configure HOI4 paths, and connect an MCP client.
4
+ - [Local references](reference.md): retrieve cited offline wiki and installed documentation sections and exact source definitions.
4
5
  - [Event chains](events.md): inspect, trace, lint, render, and compare event chains.
5
6
  - [Cross-system impact and decisions](analysis.md): trace source consumers and evaluate decision or mission scenarios.
6
7
  - [Mechanic tests and scenario suites](mechanics.md): interpret declared effect steps, check package links, and run resumable cases.
7
8
  - [Technology trees](technology.md): inspect, trace, lint, render, and compare technologies and doctrines.
9
+ - [Visual examples and comparison limits](visual-comparisons.md): real game captures beside MCP renders, with measured findings and limits.
10
+ - [Interface examples and comparison limits](https://github.com/klimPaskov/hoi4-agent-tools/blob/main/docs/gui-comparisons.md): native-size vanilla and Chaos Redux windows, populated templates, scenarios, and provenance.
8
11
  - [Bounded helper expansion](helper-expansion.md): resume source-linked event and technology helper paths with exact coverage boundaries.
9
12
  - [Persistent jobs](jobs.md): run, inspect, cancel, and recover long operations.
10
13
  - [AI and MTTH analysis](probability.md): evaluate weighted logic, timing, uncertainty, sequences, and patches.
package/docs/gui.md CHANGED
@@ -33,6 +33,12 @@ Inspection checks missing assets and localisation, invalid sizes, overlap, clipp
33
33
 
34
34
  For source comparisons, pass `sourceBaseline: { "relativePath": "interface/example.gui", "source": "..." }` to `hoi4.gui_render`. The tool renders that mod-owned `.gui` text and the current source with the same requested scenario, and returns source-baseline and source-comparison PNGs plus a JSON report with source revisions and pixel deltas. `comparisonScenario` remains the separate comparison between two scenarios on the current source.
35
35
 
36
+ GUI scenarios accept `date` in `YYYY.M.D` or `YYYY-MM-DD` form and `controls` as a map from state IDs to controller tags, for example `{"date":"1944.10.1","country":{"tag":"GER"},"controls":{"87":"GER","88":"GER"}}`. Date and controller facts reach the shared condition evaluator, including `controls_state` and `is_controlled_by` in declared state scopes. An absent state-controller fact remains unresolved. These fields are explicit scenario assumptions; they do not prove a live campaign's state or engine execution.
37
+
38
+ A requested root window uses its source `show_position` when present. This is the settled visible endpoint, so native windows whose ordinary `position` is offscreen can be inspected as open panels. The static render does not claim to reproduce the engine's transition animation.
39
+
40
+ Explicit native list rows resolve their entry template in the owning GUI file before considering a shared template, so reused names in unrelated vanilla windows do not select the wrong layout. One-column native grids with a one-pixel slot height stack entries by their rendered heights. Scenario `.text` overrides can name installed localisation keys even when those keys do not appear literally in the selected GUI source. For dimensions normally supplied by engine code, provide `values["element.width"]` or `values["element.height"]` in native UI pixels and record the source or declared-state basis for those values.
41
+
36
42
  The renderer resolves sprites and fonts through the complete mod, dependency, and installed-game load order. Mod windows therefore render ordinary vanilla sprites, tiled vanilla panels, progress bars, masked shields, button frames, country flags, and localisation icons such as `£command_power` without copying game assets into the mod. Case differences in GFX and font identifiers do not prevent a valid asset from resolving.
37
43
 
38
44
  Bitmap text uses the language-appropriate HOI4 font definition and its real glyph atlas. Header, serif, compact, typewriter, black, inverted, and other declared faces keep their own glyph shapes, native metrics, face colour, border colour, language override, and font-specific `textcolors` palette. The renderer reads BMFont channel roles, separates face and outline coverage, and recognizes both vanilla black-on-white masks and conventional white-on-transparent custom masks. HOI4 localisation colour runs such as `§Y` and `§R` therefore colour the glyph face while retaining its sharp antialiasing and declared border instead of flattening the glyph into a blurred monochrome mask or switching to the tool font. The default language uses the base font rather than an unrelated language override, and the renderer recognizes installed-game atlases whose filename omits a page suffix declared by the matching `.fnt` file.
package/docs/map.md CHANGED
@@ -82,3 +82,11 @@ Set `collision` to `reject` to require an unused destination. Set `renameLocalis
82
82
  `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.
83
83
 
84
84
  Every rewrite returns changed-area, changed-ID, semantic, affected-file, diagnostic, and rendered comparison evidence. A failed operation does not leave a partial multi-file result.
85
+
86
+ ## Dated state history
87
+
88
+ Map inspection reads active `common/bookmarks/*.txt` sources and uses the earliest valid bookmark date as the state-history basis. Supported owner, controller, core, claim, victory-point, and building commands in earlier dated blocks contribute to the inspected state and its rendered owner or controller layer. If no bookmark date is available, dated state fields retain their direct values and inspection reports `MAP_STATE_DATED_HISTORY_UNRESOLVED`.
89
+
90
+ Map rewrites preserve dated source blocks byte-for-byte. An update to a field overridden by an applicable dated block returns `MAP_DATED_STATE_HISTORY_CONFLICT`. Province membership changes also block when a dated victory point or building entry would need remapping. Splitting or merging a source state with dated history returns `MAP_DATED_STATE_DISTRIBUTION_UNSUPPORTED`, because copying or deleting that history would change its later meaning. A direct edit with only later dated changes may proceed; those later commands remain and can supersede the direct value at later bookmarks. Edit the dated source deliberately when that later behavior is the intended change.
91
+
92
+ `floating_harbor` placement rows differ from fixed `naval_base_spawn` ports. Their XYZ coordinates normally identify a sea placement, while the final numeric field names a land target. The map catalog does not count floating-harbor placements as starting naval-base locators. A missing or non-land target is an error. An off-sea position or an unusual state/coastal association is a review warning because those patterns also occur in installed vanilla map data; the warning retains the exact source line.
@@ -18,6 +18,9 @@ All seven tools are read-only. Proposed source is parsed in memory and never wri
18
18
 
19
19
  Pass a source without an adapter when the surface type is not yet known. Inspection returns every compatible adapter, candidate counts, and example candidate IDs; its discovery artifact includes every candidate ID and source location. If a requested adapter, identifier, or candidate pool does not match the source, inspection returns the same discovery result with an explanation and suggested adapter instead of `PROBABILITY_SURFACE_EMPTY`.
20
20
 
21
+ Source request paths are exact workspace-relative files, such as `common/decisions/example.txt`.
22
+ The `mod:`, `game:`, and `dependency-N:` labels in returned source citations identify origins; they are not part of a probability request's `source.path`.
23
+
21
24
  For a selected source, `hoi4.probability_inspect` includes missing source-condition fields in compact `requiredInputPaths`, adapter-wide completeness flags in `adapterRequiredInputPaths`, and the full candidate catalog, source provenance, and per-candidate requirements in its JSON resource. Unsupported source constructs remain unresolved evidence rather than being presented as scenario inputs. Evaluation reports eligible, excluded, and unresolved candidate counts inline; the resource preserves each candidate's exact eligibility and unresolved reasons for every scenario.
22
25
 
23
26
  Evaluation, sweep, simulation, and source comparison also accept an omitted adapter. When a source selector identifies one unambiguous weighted surface, the analyzer selects that source-backed adapter automatically. If a caller supplies a mismatched adapter or `custom_weighted_pool` for a source-backed request, the analyzer corrects it when the source has exactly one match. `customPoolManifest`, `beforeManifest`, and `afterManifest` use the custom-pool adapter automatically; `PROBABILITY_SURFACE_EMPTY` is reserved for sources with no weighted surface, while genuinely multi-adapter sources ask the caller to narrow by identifier or line.
@@ -94,6 +97,8 @@ Focus, technology, and doctrine probabilities require a complete candidate pool.
94
97
 
95
98
  Decision and mission adapters intentionally return scores and ranks without inventing normalized probabilities. Event-option and `random_list` adapters normalize only their complete local pools. Direct random remains an independent percentage. MTTH horizon chance uses the versioned game timing model and returns a bound when an inactive-to-active polling phase is unknown.
96
99
 
100
+ `candidateOverrides` declares candidate eligibility for a scenario. It does not force weight-modifier conditions to be true or false; those conditions still use the scenario's actual declared variables, flags, scopes, and controller facts. An overridden eligibility claim is an explicit scenario assumption, not evidence that the engine makes that candidate available.
101
+
97
102
  Named acceptance bands and configurable diagnostic thresholds let a test suite state intended probability, timing, starvation, dominance, prevalence, and sensitivity limits. Evaluate, sweep, and simulation requests can name the metrics of interest; that set is retained in result metadata while the authoritative result keeps the eligibility and trace context required to explain them. Sweeps enumerate declared alternatives exactly, add trigger breakpoints and their adjacent values for continuous ranges, and report local elasticities, pairwise interactions, rank reversals, cliffs, and missed target bands. Sweep expansion is bounded and rejected before it can silently truncate the requested analysis.
98
103
 
99
104
  ## Results
@@ -119,6 +124,8 @@ Deterministic simulation uses constant-memory Latin hypercube sampling by defaul
119
124
 
120
125
  Pass `inlineClausewitz` or `virtualPatch` in a source selector to analyze text without writing it. `hoi4.probability_compare` evaluates the before and after source under the same scenarios and attributes each changed rank, score, probability, or uncertainty to its modifier trace.
121
126
 
127
+ For a frozen file stored outside its gameplay folder, set the source selector's `path` to its logical gameplay path, `snapshotPath` to the preserved workspace-relative file, and `expectedSourceHash` to the SHA-256 of that file's exact bytes. For example, a decision copy in `docs/evidence/decisions-before.txt` can be analyzed as `common/decisions/policy.txt`. The server verifies the frozen bytes and retains their physical source provenance while using the logical path for domain recognition. It never replaces the current file. Both paths must identify exact files inside authorized roots; traversal and wildcard selectors are rejected. Shared helpers and constants still come from the current configured workspace, so one frozen file is not a complete historical workspace snapshot.
128
+
122
129
  ## Declared sequences
123
130
 
124
131
  Sequence analysis accepts a `customPoolManifest`. The manifest is the complete model boundary: candidates, selection mode, cadence, state, recovery, caps, cooldowns, removals, resets, timer changes, and terminal states. Small finite systems use exact state distributions, bounded systems expose omitted beam mass, and large systems use deterministic seeded Monte Carlo. Results include per-candidate and per-category next-choice probability, expected selections, ever-selected probability, starvation, and expected first-selection day. The analyzer never executes event effects or infers wider campaign state.
@@ -0,0 +1,32 @@
1
+ # Local references and source lookup
2
+
3
+ The reference tools read only local, authorized sources. They do not fetch the Paradox Wiki or start Hearts of Iron IV. The installed game's documentation has the highest authority for current syntax; the offline wiki supplies broader modding guidance. A result identifies its source, path, exact lines, and SHA-256 revision so an agent can inspect only the relevant section.
4
+
5
+ The server discovers these directories for a workspace:
6
+
7
+ | Source | Default location | Optional workspace registration |
8
+ | ------------------------------ | --------------------------------------------------------------------------- | ------------------------------------------------------ |
9
+ | Installed game documentation | `gameRoot/documentation/**/*.md` and `gameRoot/common/**/*documentation.md` | Configure `gameRoot`. |
10
+ | Offline wiki | `modRoot/paradox_wiki/**/*.md` | Set absolute `wikiRoot`. |
11
+ | Generated script documentation | `modRoot/script_docs/**/*.md` and `**/*.log` | Set absolute `scriptDocsRoot` to a user-supplied dump. |
12
+
13
+ Only Markdown headings and recognizable standalone names in generated `.log` files form sections. A dump that yields one section should be treated as insufficiently parsed; inspect its source format before relying on it. Source bytes are hashed on each request, while unchanged section indexes are reused. Missing roots are reported as unavailable. The public package contains none of these local source files.
14
+
15
+ ## Tools
16
+
17
+ - `hoi4.reference_context`: request a surface such as `event`, `focus`, `technology`, `gui`, or `map`. It returns only the relevant offline wiki and installed documentation citations for that surface. Supply `question` to put the five best matching sections first. `missing` names a selected source absent from the configured roots. Citations are pointers and excerpts; read the pertinent sections before changing code.
18
+ - `hoi4.reference_search`: search page names, headings, exact script tokens, and matching body lines. The default limit is six results and the maximum is twelve. Filter `sources` to `game_doc`, `wiki`, or `script_doc` when appropriate.
19
+ - `hoi4.reference_read`: provide a search/context result's `id` and `revision`. Read up to 80 lines and 8,000 UTF-8 bytes. Continue with the returned `nextLine`, `nextColumn` as `startColumn`, and the same revision. Long source lines continue without losing characters. A changed source returns `REFERENCE_REVISION_STALE`.
20
+ - `hoi4.source_lookup`: find exact definitions and indexed usages in the installed game and configured mod source. The response includes load order, override status, up to three source blocks bounded to 2,000 bytes each, a scan revision, and explicit reference counts and truncation. Follow `nextLine` and `nextColumn` as `fromLine` and `fromColumn`, with `expectedRevision`, to read more of the active definition.
21
+
22
+ Example:
23
+
24
+ ```json
25
+ { "workspaceId": "current", "surface": "event", "question": "save_event_target_as" }
26
+ ```
27
+
28
+ Call `hoi4.reference_context` with that input, then search `save_event_target_as` and pass the result's `id` and `revision` to `hoi4.reference_read`. Use `hoi4.source_lookup` for an exact event, effect, or other indexed symbol. Run `hoi4.event_inspect` and the relevant render/compare/scenario tools for the event itself; references do not validate a mod implementation.
29
+
30
+ The local index admits at most 512 files per source kind, 2 MB per file, 32 MB in total, and 20,000 sections. Search and read responses are bounded by the MCP result budget. `skipped` and per-source coverage expose incomplete ingestion instead of implying that an omitted source was checked. Registered external reference roots are operator configuration and remain private to the authorized workspace.
31
+
32
+ See [the measured Chaos Redux reference retrieval](research/reference-benchmark.md) for actual source coverage and response sizes.
@@ -0,0 +1,28 @@
1
+ # Chaos Redux reference retrieval
2
+
3
+ Measured on 2026-09-26 against the local Chaos Redux `paradox_wiki/` snapshot and the installed Hearts of Iron IV documentation. The fixture script requested context bundles for six work surfaces, then searched and read the installed `save_event_target_as` effect documentation. These are local-source measurements, not a claim about response accuracy for every future task.
4
+
5
+ | Surface | Citations | Missing required sources | Context JSON bytes | Elapsed milliseconds |
6
+ | ---------- | --------: | -----------------------: | -----------------: | -------------------: |
7
+ | Event | 10 | 0 | 5,536 | 218 |
8
+ | Decision | 7 | 0 | 3,988 | 115 |
9
+ | Focus | 8 | 0 | 4,537 | 128 |
10
+ | Technology | 6 | 0 | 3,525 | 137 |
11
+ | GUI | 6 | 0 | 3,678 | 109 |
12
+ | Map | 7 | 0 | 4,224 | 113 |
13
+
14
+ The eleven full core wiki files alone total 1,167,186 bytes in this snapshot. The task-specific context responses above used 3,525–5,536 bytes, approximately 99.5–99.7% fewer bytes than loading those eleven whole pages. This measures retrieved bytes, not tokenizer-specific tokens or complete knowledge of every page. Follow-up section reads add context as needed. Each bundle selected the relevant wiki topics and installed documentation for its work surface; no selected source was missing in these six runs.
15
+
16
+ The `save_event_target_as` search returned the installed `effects_documentation.md` section at line 6,496 first. A 12-line read returned the command name and supported scopes from that file. The exact source revision and path were returned with the citation. The server did not invoke the game or access the wiki website.
17
+
18
+ ## Natural-language discovery
19
+
20
+ Nine queries were chosen before ranking changes to cover focus OR prerequisites, technology icon sizing, event-target scope documentation, mission timeouts, scripted GUI click effects, dated state history, localisation BOM, AI focus weights, and event option weights. Each query has a manually checked answer-bearing file and section in the local sources. The original exact-phrase ranking found the expected page in the top five for 2 of 9 queries. Term-aware ranking found the expected page and section in the top five for all 9. Five answer-bearing sections ranked first; all nine ranked fourth or better. This is a small task-specific retrieval set, not a general precision guarantee.
21
+
22
+ The nine ranked result sets occupied 2,776–4,216 JSON bytes each. The dedicated run took 126–234 milliseconds per query, including source hash checks; earlier concurrent runs took up to about three seconds. `scripts/evaluate-reference.ts` reruns the page, section, context coverage, response-size, and latency checks against a supplied mod and game root. It exits with a failure when an expected answer-bearing section or required context source is missing.
23
+
24
+ Revision-bound section IDs stayed stable across unrelated retrieval calls in the fixture test, and a fresh reference-service instance reopened the same cited text without the previous cache. A changed source invalidated the old revision. This supports returning to an exact citation without keeping full pages in conversation context. It does not measure a model's memory or guarantee that every future question can be answered from one retrieved section.
25
+
26
+ The retrieval benchmark used the local service directly. A separate real-workspace stdio run identified the built server as 3.5.0 and completed documentation context in 306 ms, a cited section read in 162 ms, and a first exact source lookup in 58,851 ms. That lookup builds the broader game/mod symbol inventory; it should not be confused with the much narrower documentation lookup. MCP transport fixtures cover both protocol adapters, workspace authorization, read-scope denial, and source usage discovery. Individual task adequacy and actual engine behavior remain separate checks.
27
+
28
+ The four additional public tool definitions add approximately 6.5 KB to the one-time MCP tool discovery response. The complete list is about 72 KB in the discovery fixture and is tested against a 72 KiB ceiling. The context reduction above applies to reference retrieval after discovery.
package/docs/setup.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Setup
2
2
 
3
+ For Codex, the supplied registration allows 120 seconds for startup and 600 seconds per tool call.
4
+ Large source scans and GUI renders can take several minutes; keep selectors and scenario matrices bounded and use negotiated task support when available.
5
+ `tool_timeout_sec` is the per-server tool deadline described in the [official configuration reference](https://developers.openai.com/ja-JP/docs/config-file/config-reference).
6
+
3
7
  HOI4 Agent Tools requires Node.js 22.19 or later in the Node 22 line, or Node.js 24.
4
8
 
5
9
  ## Install
@@ -33,6 +37,8 @@ hoi4-agent-tools-setup --init --mod-root /projects/hoi4-mods --mod-root /worksho
33
37
 
34
38
  The corresponding config fields are `modRoots`, `gameRoot`, and `workspaceStorageRoot`.
35
39
 
40
+ For a configured workspace, `wikiRoot` can select an absolute path to an offline Paradox Wiki snapshot and `scriptDocsRoot` can select an absolute path to a user-supplied generated documentation dump. Without these fields, the reference tools look for `paradox_wiki/` and `script_docs/` inside the mod. See [Local references](reference.md).
41
+
36
42
  Linked directory entries are not followed. If a mod folder is a symlink or junction, configure the real parent directory as another `--mod-root`.
37
43
 
38
44
  ## MCP client registration
@@ -1,3 +1,5 @@
1
1
  [mcp_servers.hoi4_agent_tools]
2
2
  command = "npx"
3
3
  args = ["-y", "hoi4-agent-tools@latest"]
4
+ startup_timeout_sec = 120
5
+ tool_timeout_sec = 600
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": "3.4.1",
4
+ "version": "3.5.0",
5
5
  "description": "Analyze HOI4 events, decisions, impact, tech, AI, and MTTH; build focus trees, GUIs, and maps.",
6
6
  "type": "module",
7
7
  "license": "Apache-2.0",
@@ -49,11 +49,12 @@
49
49
  "docs/focus.md",
50
50
  "docs/gui.md",
51
51
  "docs/map.md",
52
+ "docs/reference.md",
53
+ "docs/research/reference-benchmark.md",
52
54
  "docs/http.md",
53
55
  "docs/development.md",
54
56
  "docs/jobs.md",
55
57
  "docs/helper-expansion.md",
56
- "docs/images/readme/",
57
58
  "examples/clients/",
58
59
  "examples/probability/",
59
60
  "schemas/",
@@ -83,6 +84,7 @@
83
84
  "test:coverage": "vitest run --coverage",
84
85
  "benchmark": "node --expose-gc --import tsx scripts/benchmark.ts",
85
86
  "docs:examples": "tsx scripts/generate-readme-examples.ts",
87
+ "docs:comparison-examples": "tsx scripts/generate-real-visual-comparisons.ts",
86
88
  "fixtures:generate": "npm run fixtures:generate:focus && npm run fixtures:generate:gui && npm run fixtures:generate:map && npm run fixtures:generate:event && npm run fixtures:generate:technology && npm run fixtures:generate:probability",
87
89
  "fixtures:generate:focus": "tsx scripts/generate-fixtures.ts",
88
90
  "fixtures:generate:gui": "tsx scripts/generate-gui-fixture.ts",