hoi4-agent-tools 2.5.2 → 2.6.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.
- package/CHANGELOG.md +13 -0
- package/README.md +5 -5
- package/dist/hoi4_agent_tools/core/index.js +22 -6
- package/dist/hoi4_agent_tools/focus/compact.js +103 -12
- package/dist/hoi4_agent_tools/focus/continuous-render.js +4 -10
- package/dist/hoi4_agent_tools/focus/layout.js +15 -7
- package/dist/hoi4_agent_tools/focus/render.js +2 -8
- package/dist/hoi4_agent_tools/gui/dds.js +5 -2
- package/dist/hoi4_agent_tools/gui/layout.js +1 -1
- package/dist/hoi4_agent_tools/gui/renderer.js +4 -16
- package/dist/hoi4_agent_tools/gui/source-graph.js +9 -8
- package/dist/hoi4_agent_tools/gui/studio.js +58 -4
- package/dist/hoi4_agent_tools/map/catalog.js +398 -0
- package/dist/hoi4_agent_tools/map/index.js +1 -0
- package/dist/hoi4_agent_tools/map/operations.js +337 -30
- package/dist/hoi4_agent_tools/map/render.js +40 -147
- package/dist/hoi4_agent_tools/map/service.js +1 -0
- package/dist/hoi4_agent_tools/mcp/tools/map.js +85 -9
- package/dist/hoi4_agent_tools/mcp/transports/http.js +4 -5
- package/dist/hoi4_agent_tools/mcp/transports/protocol-gate.js +7 -7
- package/dist/hoi4_agent_tools/schemas/map.js +69 -2
- package/dist/hoi4_agent_tools/technology/render.js +65 -19
- package/dist/hoi4_agent_tools/technology/service.js +67 -3
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/focus.md +1 -1
- package/docs/gui.md +2 -0
- package/docs/map.md +60 -23
- package/docs/technology.md +1 -1
- package/package.json +3 -2
- package/schemas/configuration.schema.json +1 -1
- package/schemas/continuous-focus-palette.schema.json +1 -1
- package/schemas/custom-weighted-pool.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/map-operation.schema.json +228 -74
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/server.json +2 -2
|
@@ -47,6 +47,7 @@ function technologyNode(graph, technologyId, placement) {
|
|
|
47
47
|
sourcePath: technology.source.path,
|
|
48
48
|
sourceLine: technology.source.location.start.line,
|
|
49
49
|
layoutSize: technology.layoutSize,
|
|
50
|
+
iconSprite: technology.icon.sprite,
|
|
50
51
|
...(placement === undefined ? {} : { placement }),
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -152,6 +153,7 @@ function viewSelection(graph, options) {
|
|
|
152
153
|
kind: 'doctrine',
|
|
153
154
|
sourcePath: definition.source.path,
|
|
154
155
|
sourceLine: definition.source.location.start.line,
|
|
156
|
+
...(definition.icon === undefined ? {} : { iconSprite: definition.icon.sprite }),
|
|
155
157
|
});
|
|
156
158
|
for (const definition of graph.doctrineDefinitions) {
|
|
157
159
|
const from = `doctrine:${definition.kind}:${definition.id}`;
|
|
@@ -203,6 +205,7 @@ function viewSelection(graph, options) {
|
|
|
203
205
|
kind: 'doctrine',
|
|
204
206
|
sourcePath: doctrine.source.path,
|
|
205
207
|
sourceLine: doctrine.source.location.start.line,
|
|
208
|
+
...(doctrine.icon === undefined ? {} : { iconSprite: doctrine.icon.sprite }),
|
|
206
209
|
});
|
|
207
210
|
for (const targetId of doctrine.exclusiveIds) {
|
|
208
211
|
const target = graph.doctrineDefinitions.find(({ id }) => id === targetId);
|
|
@@ -215,7 +218,11 @@ function viewSelection(graph, options) {
|
|
|
215
218
|
kind: target === undefined ? 'unresolved' : 'doctrine',
|
|
216
219
|
...(target === undefined
|
|
217
220
|
? {}
|
|
218
|
-
: {
|
|
221
|
+
: {
|
|
222
|
+
sourcePath: target.source.path,
|
|
223
|
+
sourceLine: target.source.location.start.line,
|
|
224
|
+
...(target.icon === undefined ? {} : { iconSprite: target.icon.sprite }),
|
|
225
|
+
}),
|
|
219
226
|
});
|
|
220
227
|
edges.push({ id: `${from}:xor:${to}`, from, to, kind: 'exclusive' });
|
|
221
228
|
}
|
|
@@ -537,7 +544,7 @@ function colours(kind) {
|
|
|
537
544
|
removed: { fill: '#4a2828', stroke: '#ef7d7d' },
|
|
538
545
|
}[kind];
|
|
539
546
|
}
|
|
540
|
-
function svgFor(titleText, positioned, edges, sourceAccurate) {
|
|
547
|
+
function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris) {
|
|
541
548
|
const maximumX = Math.max(PADDING + LARGE_NODE_WIDTH, ...positioned.map(({ x, width }) => x + width));
|
|
542
549
|
const maximumY = Math.max(HEADER_HEIGHT + LARGE_NODE_HEIGHT, ...positioned.map(({ y, height }) => y + height));
|
|
543
550
|
const width = Math.ceil(maximumX + PADDING);
|
|
@@ -566,28 +573,45 @@ function svgFor(titleText, positioned, edges, sourceAccurate) {
|
|
|
566
573
|
const nodeSvg = positioned.map((node) => {
|
|
567
574
|
const colour = colours(node.kind);
|
|
568
575
|
const small = node.layoutSize === 'small';
|
|
576
|
+
const iconDataUri = node.iconSprite === undefined ? undefined : iconDataUris[node.iconSprite];
|
|
577
|
+
const compactIcon = iconDataUri !== undefined && small && node.width <= SMALL_NODE_WIDTH;
|
|
578
|
+
const iconSize = iconDataUri === undefined
|
|
579
|
+
? 0
|
|
580
|
+
: compactIcon
|
|
581
|
+
? Math.max(1, Math.min(node.width - 12, node.height - 12))
|
|
582
|
+
: Math.max(1, Math.min(64, node.height - 16, Math.floor(node.width * 0.36)));
|
|
583
|
+
const textInset = iconDataUri === undefined ? (small ? 6 : 12) : iconSize + 18;
|
|
584
|
+
const availableTextWidth = Math.max(1, node.width - textInset - (small ? 6 : 12));
|
|
569
585
|
const labelLimit = small ? 12 : 34;
|
|
570
586
|
const label = node.label.length > labelLimit
|
|
571
587
|
? `${node.label.slice(0, Math.max(1, labelLimit - 1))}…`
|
|
572
588
|
: node.label;
|
|
573
589
|
const subtitle = node.subtitle.length > 48 ? `${node.subtitle.slice(0, 45)}…` : node.subtitle;
|
|
574
|
-
const labelSvg =
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
590
|
+
const labelSvg = compactIcon
|
|
591
|
+
? ''
|
|
592
|
+
: text.render(label, {
|
|
593
|
+
x: node.x + textInset,
|
|
594
|
+
y: node.y + (small ? 27 : 30),
|
|
595
|
+
fontSize: small ? 9 : 15,
|
|
596
|
+
fill: '#f1f5f8',
|
|
597
|
+
weight: 600,
|
|
598
|
+
targetWidth: Math.min(availableTextWidth, Math.max(1, text.measure(label, small ? 9 : 15))),
|
|
599
|
+
});
|
|
600
|
+
const subtitleSvg = compactIcon
|
|
601
|
+
? ''
|
|
602
|
+
: text.render(small ? 'SMALL' : subtitle, {
|
|
603
|
+
x: node.x + textInset,
|
|
604
|
+
y: node.y + (small ? 51 : 58),
|
|
605
|
+
fontSize: small ? 8 : 11,
|
|
606
|
+
fill: '#b8c4ce',
|
|
607
|
+
targetWidth: Math.min(availableTextWidth, Math.max(1, text.measure(small ? 'SMALL' : subtitle, small ? 8 : 11))),
|
|
608
|
+
});
|
|
609
|
+
const iconSvg = iconDataUri === undefined
|
|
610
|
+
? ''
|
|
611
|
+
: `<image href="${escapeXml(iconDataUri)}" x="${node.x + (compactIcon ? 6 : 8)}" y="${node.y + (node.height - iconSize) / 2}" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet"/>`;
|
|
589
612
|
const layoutAttribute = node.layoutSize === undefined ? '' : ` data-layout-size="${node.layoutSize}"`;
|
|
590
|
-
|
|
613
|
+
const iconAttribute = node.iconSprite === undefined ? '' : ` data-icon-sprite="${escapeXml(node.iconSprite)}"`;
|
|
614
|
+
return `<g data-node-id="${escapeXml(node.id)}"${layoutAttribute}${iconAttribute}${node.sourcePath === undefined ? '' : ` data-source-path="${escapeXml(node.sourcePath)}" data-source-line="${node.sourceLine ?? 1}"`}><rect x="${node.x}" y="${node.y}" width="${node.width}" height="${node.height}" rx="8" fill="${colour.fill}" stroke="${colour.stroke}" stroke-width="2"/><title>${escapeXml(`${node.id}${node.layoutSize === undefined ? '' : ` · ${node.layoutSize} layout`}${node.sourcePath === undefined ? '' : ` · ${node.sourcePath}:${node.sourceLine ?? 1}`}`)}</title>${iconSvg}${labelSvg}${subtitleSvg}</g>`;
|
|
591
615
|
});
|
|
592
616
|
const title = text.render(titleText, {
|
|
593
617
|
x: PADDING,
|
|
@@ -616,7 +640,13 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
616
640
|
const positioned = selection.sourceAccurate
|
|
617
641
|
? layoutFolder(retained)
|
|
618
642
|
: layoutGenerated(retained, edges);
|
|
619
|
-
const
|
|
643
|
+
const iconDataUris = options.iconDataUris ?? {};
|
|
644
|
+
const requestedIconSprites = [
|
|
645
|
+
...new Set(retained.flatMap(({ iconSprite }) => (iconSprite === undefined ? [] : [iconSprite]))),
|
|
646
|
+
].sort(compareCodeUnits);
|
|
647
|
+
const renderedIconSprites = requestedIconSprites.filter((sprite) => iconDataUris[sprite] !== undefined);
|
|
648
|
+
const unresolvedIconSprites = requestedIconSprites.filter((sprite) => iconDataUris[sprite] === undefined);
|
|
649
|
+
const rendered = svgFor(selection.title, positioned, edges, selection.sourceAccurate, iconDataUris);
|
|
620
650
|
const budget = options.budget ?? new RenderBudget();
|
|
621
651
|
budget.reserve(rendered.width, rendered.height, `technology ${options.view} render`);
|
|
622
652
|
budget.reserveRasterOperation(`technology-render:${graph.revision}:${options.view}:${sha256Bytes(rendered.svg)}`, `technology ${options.view} rasterization`);
|
|
@@ -631,6 +661,11 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
631
661
|
nodes: retained,
|
|
632
662
|
edges,
|
|
633
663
|
omittedNodeCount: Math.max(0, selection.nodes.length - retained.length),
|
|
664
|
+
iconCoverage: {
|
|
665
|
+
requested: requestedIconSprites.length,
|
|
666
|
+
rendered: renderedIconSprites.length,
|
|
667
|
+
unresolvedSprites: unresolvedIconSprites,
|
|
668
|
+
},
|
|
634
669
|
render: { width: rendered.width, height: rendered.height },
|
|
635
670
|
})}\n`;
|
|
636
671
|
const html = options.includeHtml === true ? htmlFor(selection.title, rendered.svg, json) : undefined;
|
|
@@ -639,6 +674,8 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
639
674
|
graphRevision: graph.revision,
|
|
640
675
|
selectedIds: retained.map(({ id }) => id),
|
|
641
676
|
omittedNodeCount: Math.max(0, selection.nodes.length - retained.length),
|
|
677
|
+
renderedIconCount: renderedIconSprites.length,
|
|
678
|
+
unresolvedIconSprites,
|
|
642
679
|
sourceAccurate: selection.sourceAccurate,
|
|
643
680
|
generatedAnalysisLayout: !selection.sourceAccurate,
|
|
644
681
|
width: rendered.width,
|
|
@@ -655,3 +692,12 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
655
692
|
},
|
|
656
693
|
};
|
|
657
694
|
}
|
|
695
|
+
/** Return only the sprite names that the bounded render selection will display. */
|
|
696
|
+
export function technologyRenderIconSprites(graph, options) {
|
|
697
|
+
const maximum = Math.max(1, Math.min(options.maxNodes ?? 600, MAX_RENDER_NODES));
|
|
698
|
+
return [
|
|
699
|
+
...new Set(viewSelection(graph, options)
|
|
700
|
+
.nodes.slice(0, maximum)
|
|
701
|
+
.flatMap(({ iconSprite }) => (iconSprite === undefined ? [] : [iconSprite]))),
|
|
702
|
+
].sort(compareCodeUnits);
|
|
703
|
+
}
|
|
@@ -6,10 +6,12 @@ import { ServiceError } from '../core/result.js';
|
|
|
6
6
|
import { SOURCE_MAX_BYTES } from '../core/source/index.js';
|
|
7
7
|
import { isPortablePathSegment } from '../core/workspace.js';
|
|
8
8
|
import { PACKAGE_VERSION } from '../version.js';
|
|
9
|
+
import { GuiAssetCatalog } from '../gui/assets.js';
|
|
10
|
+
import { buildGuiSourceGraph } from '../gui/source-graph.js';
|
|
9
11
|
import { compareTechnologyGraphs } from './compare.js';
|
|
10
12
|
import { buildTechnologyGraph, technologyAssetPatterns } from './graph.js';
|
|
11
13
|
import { analyzeTechnologyImpact, discoverTechnologyFolders, explainTechnology, inspectTechnologyUnlocks, lintTechnologyGraph, technologyBonusCoverage, technologyScanReport, traceTechnology, } from './queries.js';
|
|
12
|
-
import { renderTechnologyGraph, } from './render.js';
|
|
14
|
+
import { renderTechnologyGraph, technologyRenderIconSprites, } from './render.js';
|
|
13
15
|
const FRAGMENT_CACHE_ENTRIES = 8_192;
|
|
14
16
|
const FRAGMENT_CACHE_BYTES = 134_217_728;
|
|
15
17
|
const PROPOSED_SOURCE_FILES = 128;
|
|
@@ -80,10 +82,54 @@ function technologyScanPatterns(workspace) {
|
|
|
80
82
|
'history/countries/**/*.txt',
|
|
81
83
|
...under(roots.focus, '**/*.txt'),
|
|
82
84
|
...under(roots.interface, '**/*.gui'),
|
|
85
|
+
...under(roots.interface, '**/*.gfx'),
|
|
86
|
+
...under(roots.gfx, '**/*.gfx'),
|
|
83
87
|
...under(roots.localisation, 'english/**/*.{yml,yaml}'),
|
|
84
88
|
...under(roots.localisation, '*.{yml,yaml}'),
|
|
85
89
|
].sort(compareCodeUnits);
|
|
86
90
|
}
|
|
91
|
+
function activeTechnologySprite(spriteName, spritePath, sprites) {
|
|
92
|
+
return sprites.find((sprite) => sprite.name === spriteName && (spritePath === undefined || sprite.sourcePath === spritePath));
|
|
93
|
+
}
|
|
94
|
+
async function resolveTechnologyIconDataUris(cached, requestedSprites, budget, signal) {
|
|
95
|
+
const icons = [
|
|
96
|
+
...cached.graph.technologies.map(({ icon }) => icon),
|
|
97
|
+
...cached.graph.doctrineDefinitions.flatMap(({ icon }) => icon ?? []),
|
|
98
|
+
].filter(({ sprite }) => requestedSprites.has(sprite));
|
|
99
|
+
const spriteSourcePaths = new Set(icons.flatMap(({ spritePath }) => (spritePath === undefined ? [] : [spritePath])));
|
|
100
|
+
const spriteFiles = cached.snapshot.files.filter(({ displayPath }) => spriteSourcePaths.has(displayPath));
|
|
101
|
+
if (spriteFiles.length === 0)
|
|
102
|
+
return {};
|
|
103
|
+
const graph = buildGuiSourceGraph(spriteFiles, cached.snapshot.index);
|
|
104
|
+
const files = new Map();
|
|
105
|
+
for (const file of [...spriteFiles, ...cached.assetFiles])
|
|
106
|
+
files.set(file.displayPath, file);
|
|
107
|
+
const catalog = new GuiAssetCatalog(graph, [...files.values()], budget);
|
|
108
|
+
const unique = new Map();
|
|
109
|
+
for (const icon of icons)
|
|
110
|
+
if (!unique.has(icon.sprite))
|
|
111
|
+
unique.set(icon.sprite, icon);
|
|
112
|
+
const entries = [...unique.entries()].sort(([left], [right]) => compareCodeUnits(left, right));
|
|
113
|
+
const dataUris = {};
|
|
114
|
+
let cursor = 0;
|
|
115
|
+
await Promise.all(Array.from({ length: Math.min(8, entries.length) }, async () => {
|
|
116
|
+
while (cursor < entries.length) {
|
|
117
|
+
signal?.throwIfAborted();
|
|
118
|
+
const entry = entries[cursor];
|
|
119
|
+
cursor += 1;
|
|
120
|
+
if (entry === undefined)
|
|
121
|
+
continue;
|
|
122
|
+
const [spriteName, icon] = entry;
|
|
123
|
+
const sprite = activeTechnologySprite(spriteName, icon.spritePath, graph.sprites);
|
|
124
|
+
if (sprite === undefined)
|
|
125
|
+
continue;
|
|
126
|
+
const frame = await catalog.loadSpriteFrame(sprite, 0);
|
|
127
|
+
if (frame?.supported === true && frame.dataUri !== undefined)
|
|
128
|
+
dataUris[spriteName] = frame.dataUri;
|
|
129
|
+
}
|
|
130
|
+
}));
|
|
131
|
+
return Object.fromEntries(Object.entries(dataUris).sort(([left], [right]) => compareCodeUnits(left, right)));
|
|
132
|
+
}
|
|
87
133
|
function technologyAnalysisMode(workspace, snapshot) {
|
|
88
134
|
return workspace.gameRoot !== undefined || snapshot.files.length > FOCUSED_GRAPH_FILE_THRESHOLD
|
|
89
135
|
? 'focused'
|
|
@@ -462,6 +508,9 @@ export class TechnologyTreeViewer {
|
|
|
462
508
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
463
509
|
});
|
|
464
510
|
const budget = new RenderBudget();
|
|
511
|
+
const cached = this.#state.current.get(input.workspaceId);
|
|
512
|
+
if (cached?.graph.revision !== graph.revision)
|
|
513
|
+
throw new ServiceError('TECH_RENDER_SOURCE_MISSING', 'Technology render source inventory is unavailable for the current graph');
|
|
465
514
|
const renderOptions = {
|
|
466
515
|
view: input.view,
|
|
467
516
|
maxNodes: input.maxNodes ?? 1_000,
|
|
@@ -473,13 +522,23 @@ export class TechnologyTreeViewer {
|
|
|
473
522
|
...(input.targetId === undefined ? {} : { targetId: input.targetId }),
|
|
474
523
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
475
524
|
};
|
|
476
|
-
const render = await renderTechnologyGraph(graph, renderOptions);
|
|
477
|
-
const focused = [];
|
|
478
525
|
const focusedFolders = input.view === 'dependencies' && input.folderId === undefined
|
|
479
526
|
? graph.folders
|
|
480
527
|
.filter((folder) => graph.placements.some(({ folderId }) => folderId === folder.id))
|
|
481
528
|
.slice(0, FOCUSED_RENDER_LIMIT)
|
|
482
529
|
: [];
|
|
530
|
+
const requestedIconSprites = new Set([
|
|
531
|
+
...technologyRenderIconSprites(graph, renderOptions),
|
|
532
|
+
...focusedFolders.flatMap(({ id }) => technologyRenderIconSprites(graph, {
|
|
533
|
+
view: 'folder',
|
|
534
|
+
folderId: id,
|
|
535
|
+
maxNodes: 1_000,
|
|
536
|
+
})),
|
|
537
|
+
]);
|
|
538
|
+
const iconDataUris = await resolveTechnologyIconDataUris(cached, requestedIconSprites, budget, input.signal);
|
|
539
|
+
renderOptions.iconDataUris = iconDataUris;
|
|
540
|
+
const render = await renderTechnologyGraph(graph, renderOptions);
|
|
541
|
+
const focused = [];
|
|
483
542
|
for (const folder of focusedFolders) {
|
|
484
543
|
input.signal?.throwIfAborted();
|
|
485
544
|
focused.push(await renderTechnologyGraph(graph, {
|
|
@@ -487,6 +546,7 @@ export class TechnologyTreeViewer {
|
|
|
487
546
|
folderId: folder.id,
|
|
488
547
|
maxNodes: 1_000,
|
|
489
548
|
includeHtml: false,
|
|
549
|
+
iconDataUris,
|
|
490
550
|
budget,
|
|
491
551
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
492
552
|
}));
|
|
@@ -516,6 +576,8 @@ export class TechnologyTreeViewer {
|
|
|
516
576
|
omittedNodeCount: render.omittedNodeCount,
|
|
517
577
|
sourceAccurate: render.sourceAccurate,
|
|
518
578
|
generatedAnalysisLayout: render.generatedAnalysisLayout,
|
|
579
|
+
renderedIconCount: render.renderedIconCount,
|
|
580
|
+
unresolvedIconSprites: render.unresolvedIconSprites,
|
|
519
581
|
hashes: render.hashes,
|
|
520
582
|
},
|
|
521
583
|
focusedFolders: focused.map((bundle, index) => ({
|
|
@@ -523,6 +585,8 @@ export class TechnologyTreeViewer {
|
|
|
523
585
|
folderId: focusedFolders[index]?.id,
|
|
524
586
|
selectedIds: bundle.selectedIds,
|
|
525
587
|
omittedNodeCount: bundle.omittedNodeCount,
|
|
588
|
+
renderedIconCount: bundle.renderedIconCount,
|
|
589
|
+
unresolvedIconSprites: bundle.unresolvedIconSprites,
|
|
526
590
|
hashes: bundle.hashes,
|
|
527
591
|
})),
|
|
528
592
|
focusedFolderCoverage: {
|
|
@@ -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 = '2.
|
|
3
|
+
export const PACKAGE_VERSION = '2.6.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/focus.md
CHANGED
|
@@ -30,7 +30,7 @@ For a layout-only cleanup of an existing national tree, call `hoi4.focus_rewrite
|
|
|
30
30
|
|
|
31
31
|
Compact mode imports the tree, resets authored presentation coordinates and lane bounds, and performs a deterministic reflow in the same rewrite call. It keeps prerequisites, mutual exclusions, availability, bypasses, rewards, icons, localisation, AI behavior, and route/lane membership unchanged.
|
|
32
32
|
|
|
33
|
-
The rewrite measures canvas bounds, same-row spacing, the same curved connector paths used by the renderer, connector lengths, connector-node intersections, branch balance, overall centering, and topology-aware fake-complexity patterns. Compact mode evaluates preservation, row repair, linear-chain repair, sibling balancing, and combined compression strategies, then chooses the cleanest valid result. Sparse direct sibling fan-outs are packed around their parent at the minimum readable spacing, with each exclusive descendant branch moving as a unit and shared convergence focuses staying centered. Dense branches remain wider only when their contents need the room. It refuses invalid geometry such as overlapping or cramped focuses, bad parent order, rendered crossings, connectors through unrelated focuses, or an off-center canvas. Long connectors, fake detours, staircase or zigzag chains, and avoidable sibling imbalance are optimization priorities instead of independent rejection budgets.
|
|
33
|
+
The rewrite measures canvas bounds, same-row spacing, the same curved connector paths used by the renderer, connector lengths, connector-node intersections, branch balance, overall centering, and topology-aware fake-complexity patterns. Compact mode evaluates preservation, occupied-row repair, prerequisite-depth reflow, linear-chain repair, sibling balancing, and combined compression strategies, then chooses the cleanest valid result. Equivalent branches are aligned by dependency depth even when their authored rows differ, and connector shape is prioritized before raw canvas dimensions so a shorter canvas cannot win by introducing unnecessary horizontal detours. Sparse direct sibling fan-outs are packed around their parent at the minimum readable spacing, with each exclusive descendant branch moving as a unit and shared convergence focuses staying centered. Dense branches remain wider only when their contents need the room. It refuses invalid geometry such as overlapping or cramped focuses, bad parent order, rendered crossings, connectors through unrelated focuses, or an off-center canvas. Long connectors, fake detours, staircase or zigzag chains, and avoidable sibling imbalance are optimization priorities instead of independent rejection budgets.
|
|
34
34
|
|
|
35
35
|
Use the default `layoutMode: "authored"` with a complete plan when deliberately designing coordinates or changing gameplay. Set `layoutMode: "compact"` with a complete plan when creating a large automatically arranged tree. Inspect and render calls remain useful before or after either rewrite mode, but a separate plan is not required for compact cleanup.
|
|
36
36
|
|
package/docs/gui.md
CHANGED
|
@@ -52,6 +52,8 @@ Selectors accept an element name, instance ID, or source ID. Visibility expectat
|
|
|
52
52
|
|
|
53
53
|
Broad inspection indexes localisation actually referenced by GUI source and returns a connected workspace projection when the complete source graph is very large. The resource records full and returned node and edge counts, so an agent can identify the relevant window without loading unrelated vanilla UI into its prompt. Rendering remains targeted to the selected root, state, and resolution.
|
|
54
54
|
|
|
55
|
+
GUI rewrites keep complete validation evidence in a linked JSON resource and retain the highest-priority findings in the transaction result. Large surrounding interface inventories therefore do not turn a small scalar patch into a transaction structure-limit error.
|
|
56
|
+
|
|
55
57
|
## Offline preview limits
|
|
56
58
|
|
|
57
59
|
The renderer does not run the game engine. Each render includes a fidelity report that separates fields it models from fields it approximates, ignores, cannot resolve, or does not support. Treat that report as part of the review.
|
package/docs/map.md
CHANGED
|
@@ -2,38 +2,75 @@
|
|
|
2
2
|
|
|
3
3
|
Use `hoi4.map_inspect`, `hoi4.map_render`, and `hoi4.map_rewrite` for provinces, states, strategic regions, adjacency, supply nodes, railways, positions, ownership, cores, claims, resources, buildings, and localisation.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Inspect and navigate the complete map
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
`hoi4.map_inspect` validates the map 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
8
|
|
|
9
|
-
|
|
9
|
+
The linked JSON catalog contains every active province definition, state, and strategic region with resolved names, alternate localisation values, source paths and lines, raster bounds and centers, memberships, neighbors, victory points, resources, buildings, ports, supply nodes, railways, normal and special adjacencies, building positions, unit positions, weather positions, and entity locators. Large catalog and render data remain in MCP resources instead of filling the agent prompt.
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
Use `query` for a compact name or ID result list and `coordinates` for exact pixel or map-coordinate lookup. Use `provinceIds`, `stateIds`, and `regionIds` for focused records. Up to 32 selected provinces can also produce exact geometry as maximal `[y, startX, endXExclusive]` row runs.
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
`hoi4.map_render` creates the same complete catalog and navigator with a chosen base layer and overlays. Base layers include province, state, strategic region, terrain, continent, owner, controller, cores, claims, and coast. Overlays include coastlines, ports, victory points, resources, state and province buildings, supply nodes, railways, adjacencies, and building, unit, and weather positions.
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
- manpower, resources, buildings, and victory points;
|
|
17
|
-
- owner, controller, cores, and claims;
|
|
18
|
-
- ports, supply nodes, and railways;
|
|
19
|
-
- building, unit, weather, and entity positions;
|
|
20
|
-
- normal and special adjacency;
|
|
21
|
-
- state localisation.
|
|
15
|
+
## Create states
|
|
22
16
|
|
|
23
|
-
|
|
17
|
+
The compact `create_state` form needs selected provinces and a display name:
|
|
24
18
|
|
|
25
|
-
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"id": "create-western-state",
|
|
22
|
+
"kind": "create_state",
|
|
23
|
+
"provinceIds": [120, 121, 122],
|
|
24
|
+
"displayName": "Western Ireland"
|
|
25
|
+
}
|
|
26
|
+
```
|
|
26
27
|
|
|
27
|
-
|
|
28
|
-
2. Render a baseline with the relevant province, state, region, terrain, coastline, supply, railway, ownership, or building overlays.
|
|
29
|
-
3. Build one ordered list of exact repair operations.
|
|
30
|
-
4. Supply every required geometry and data-movement choice.
|
|
31
|
-
5. Rewrite, then inspect and render the result.
|
|
28
|
+
When all selected provinces belong to one state, the tool infers that source state, allocates the state ID, creates `STATE_<id>` localisation, divides manpower, resources, and state buildings by land-pixel share, copies owner, controller, cores, and claims, and moves province-bound records with their provinces. Supply, railway, port, victory-point, building-position, and strategic-region references remain connected.
|
|
32
29
|
|
|
33
|
-
|
|
30
|
+
Pass `sourceStateId`, `stateId`, `name`, `fileName`, `localisation`, or the complete distribution object when the defaults are not the intended design. `split_state` retains the explicit contract for callers that want every policy written out.
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
## Create provinces
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
The compact `create_province` form needs a source province and exact geometry:
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
```json
|
|
37
|
+
{
|
|
38
|
+
"id": "create-western-province",
|
|
39
|
+
"kind": "create_province",
|
|
40
|
+
"sourceProvinceId": 4812,
|
|
41
|
+
"geometry": {
|
|
42
|
+
"kind": "rectangle",
|
|
43
|
+
"origin": { "x": 2330, "y": 740 },
|
|
44
|
+
"width": 12,
|
|
45
|
+
"height": 9
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Geometry may be a rectangle, an even-odd polygon, explicit pixels, or a hash-bound raster mask. The tool allocates an unused contiguous province ID and unused RGB color after scanning game, dependencies, and mod sources. By default it inherits the source definition, state and strategic-region membership, and retains connected data on the source unless the new province itself must receive it.
|
|
51
|
+
|
|
52
|
+
Pass `provinceId`, `definition`, or the full distribution object when an exact override is needed. All selected pixels must belong to the named source province; this keeps a creation request deterministic.
|
|
53
|
+
|
|
54
|
+
## Change IDs
|
|
55
|
+
|
|
56
|
+
Use `renumber_map_entity` for `province`, `state`, or `strategic-region` IDs:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"id": "swap-state-ids",
|
|
61
|
+
"kind": "renumber_map_entity",
|
|
62
|
+
"entity": "state",
|
|
63
|
+
"fromId": 12,
|
|
64
|
+
"toId": 18
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
If the destination exists, the default behavior swaps the two IDs. Province swaps update definitions, state and region membership, victory points, province buildings, adjacency, supply nodes, railways, unit positions, building sea references, and standard province and victory-point localisation keys. State swaps update state records, standard state localisation keys, and building-position state references. Strategic-region swaps update region records, standard localisation keys, and weather-position references. Province and strategic-region operations preserve their required contiguous ID sets.
|
|
69
|
+
|
|
70
|
+
Set `collision` to `reject` to require an unused destination. Set `renameLocalisation` to `false` only when custom localisation-key handling is intentional.
|
|
71
|
+
|
|
72
|
+
## Other map changes
|
|
73
|
+
|
|
74
|
+
`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.
|
|
75
|
+
|
|
76
|
+
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.
|
package/docs/technology.md
CHANGED
|
@@ -51,7 +51,7 @@ Game-backed and otherwise large workspaces automatically use focused analysis mo
|
|
|
51
51
|
|
|
52
52
|
`hoi4.tech_render` returns authoritative JSON with deterministic SVG and PNG resources; set `includeHtml` when a bundled static report is useful. Available views are `summary`, `folder`, `dependencies`, `technology`, `doctrine`, `exclusive`, `memberships`, `bonuses`, `grants`, `unlocks`, `metadata`, `assets`, and `unresolved`.
|
|
53
53
|
|
|
54
|
-
Folder renders use the actual folder assignments, gridbox geometry, technology coordinates, and HOI4 item size found in source. Technologies with `force_use_small_tech_layout = yes` render as
|
|
54
|
+
Folder renders use the actual folder assignments, gridbox geometry, technology coordinates, and HOI4 item size found in source. Technologies with `force_use_small_tech_layout = yes` render as compact icon items; normal technologies render as large items with their icon and label. Sprite declarations and texture files resolve through the active load order across the mod, configured dependencies, and the detected or configured game installation, so vanilla icons used by a mod appear in SVG, PNG, and HTML artifacts. The authoritative JSON and SVG expose `layoutSize`, rendered-icon coverage, and unresolved sprite names without embedding image data in JSON. Dependency and other semantic views are explicitly labelled as generated analysis layouts.
|
|
55
55
|
|
|
56
56
|
Example folder render:
|
|
57
57
|
|
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": "2.
|
|
4
|
+
"version": "2.6.0",
|
|
5
5
|
"description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -69,6 +69,7 @@
|
|
|
69
69
|
"format": "prettier --write .",
|
|
70
70
|
"format:check": "prettier --check .",
|
|
71
71
|
"test": "vitest run",
|
|
72
|
+
"test:all": "vitest run --shard=1/8 && vitest run --shard=2/8 && vitest run --shard=3/8 && vitest run --shard=4/8 && vitest run --shard=5/8 && vitest run --shard=6/8 && vitest run --shard=7/8 && vitest run --shard=8/8",
|
|
72
73
|
"test:unit": "vitest run tests/unit",
|
|
73
74
|
"test:integration": "vitest run tests/integration",
|
|
74
75
|
"test:local": "tsx scripts/run-local-tests.ts",
|
|
@@ -91,7 +92,7 @@
|
|
|
91
92
|
"release:order:verify": "tsx scripts/verify-npm-release-order.ts",
|
|
92
93
|
"pack:check": "tsx scripts/check-package.ts",
|
|
93
94
|
"inspector": "tsx scripts/run-inspector.ts",
|
|
94
|
-
"check": "npm run format:check && npm run lint && npm run typecheck && npm run test && npm run fixtures:check && npm run schemas:check && npm run build && npm run pack:check && npm run registry:validate",
|
|
95
|
+
"check": "npm run format:check && npm run lint && npm run typecheck && npm run test:all && npm run fixtures:check && npm run schemas:check && npm run build && npm run pack:check && npm run registry:validate",
|
|
95
96
|
"prepack": "npm run build"
|
|
96
97
|
},
|
|
97
98
|
"dependencies": {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.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/v2.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.6.0/schemas/gui-scenario.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|