hoi4-agent-tools 3.0.0 → 3.0.2
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 +12 -0
- package/README.md +3 -1
- package/dist/bin/stdio.js +4 -0
- package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +12 -0
- package/dist/hoi4_agent_tools/gui/assets.js +26 -14
- package/dist/hoi4_agent_tools/gui/renderer.js +4 -11
- package/dist/hoi4_agent_tools/gui/scenario.js +7 -1
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +483 -0
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +6 -1
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +9 -5
- package/dist/hoi4_agent_tools/probability/service.js +48 -6
- package/dist/hoi4_agent_tools/technology/compare.js +6 -1
- package/dist/hoi4_agent_tools/technology/graph.js +67 -2
- package/dist/hoi4_agent_tools/technology/model.js +1 -1
- package/dist/hoi4_agent_tools/technology/queries.js +4 -0
- package/dist/hoi4_agent_tools/technology/render.js +73 -28
- package/dist/hoi4_agent_tools/technology/service.js +4 -1
- package/dist/hoi4_agent_tools/technology/source-analysis.js +102 -19
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/gui.md +2 -0
- package/docs/images/readme/event-chain-mcp.png +0 -0
- package/docs/images/readme/probability-mcp.png +0 -0
- package/docs/images/readme/technology-tree-mcp.png +0 -0
- package/docs/probability.md +3 -1
- package/docs/technology.md +1 -1
- package/package.json +1 -1
- 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 +59 -55
- package/schemas/map-operation.schema.json +1 -1
- 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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,18 @@
|
|
|
4
4
|
|
|
5
5
|
No unreleased changes.
|
|
6
6
|
|
|
7
|
+
## 3.0.2 - 2026-08-31
|
|
8
|
+
|
|
9
|
+
- Render enlarged HOI4 bitmap glyphs as pre-scaled atlas masks so VPS and Linux output stays crisp instead of depending on platform SVG interpolation.
|
|
10
|
+
- Apply bitmap-font face, border, and localisation colours with portable alpha masks instead of SVG colour-matrix filters that could be ignored by remote rasterizers.
|
|
11
|
+
- Clarify that GUI `uiScale` represents the in-game layout scale and is not an artifact zoom control.
|
|
12
|
+
|
|
13
|
+
## 3.0.1 - 2026-08-31
|
|
14
|
+
|
|
15
|
+
- Render technology folders with the source GUI's actual small and regular item geometry, infer small items from equipment unlock behavior, and include horizontal or vertical year guides.
|
|
16
|
+
- Restore the opt-in `chaosx.focus_country_assets` and `chaosx.visual_revision` tools with direct and stdio discovery coverage.
|
|
17
|
+
- Infer unambiguous source-backed probability adapters for evaluate, sweep, simulation, and comparison calls, including calls that previously failed with `PROBABILITY_MANIFEST_REQUIRED` or `PROBABILITY_SURFACE_EMPTY` after a mismatched adapter selection.
|
|
18
|
+
|
|
7
19
|
## 3.0.0 - 2026-08-31
|
|
8
20
|
|
|
9
21
|
- Promote the complete focus, GUI, map, event, technology, and probability MCP surface to the stable 3.0 release line after full synthetic, installed-game, external-workspace, transport, package, Inspector, and Registry qualification.
|
package/README.md
CHANGED
|
@@ -72,7 +72,7 @@ On non-Windows systems, use `hoi4-agent-tools` as the command. Agentic HOI4 repo
|
|
|
72
72
|
| `hoi4.event_render` | Render source-linked event routes, options, timing, state, scope, and unresolved edges. |
|
|
73
73
|
| `hoi4.event_compare` | Compare event-chain topology and diagnostics between revisions. |
|
|
74
74
|
| `hoi4.tech_inspect` | Scan, trace, explain, lint, and assess technology and doctrine systems. |
|
|
75
|
-
| `hoi4.tech_render` | Render source layouts
|
|
75
|
+
| `hoi4.tech_render` | Render source layouts with real item sizes and year guides, plus dependencies and assets. |
|
|
76
76
|
| `hoi4.tech_compare` | Compare technology graphs, placements, references, diagnostics, and source overlays. |
|
|
77
77
|
| `hoi4.probability_inspect` | Locate weighted logic and discover compatible adapters, candidates, and required inputs. |
|
|
78
78
|
| `hoi4.probability_evaluate` | Evaluate exact values, probabilities, timing, bounds, and unresolved inputs. |
|
|
@@ -82,6 +82,8 @@ On non-Windows systems, use `hoi4-agent-tools` as the command. Agentic HOI4 repo
|
|
|
82
82
|
| `hoi4.probability_compare` | Attribute AI-weight and MTTH changes between real or proposed source. |
|
|
83
83
|
| `hoi4.probability_render` | Render cached rankings, matrices, timing, sensitivity, sequence, and comparisons. |
|
|
84
84
|
|
|
85
|
+
Set `HOI4_AGENT_TOOLS_CHAOSX=1` on the server process to expose the optional `chaosx.focus_country_assets` and `chaosx.visual_revision` tools used by ChaosX workflows. They remain absent from the default public tool list.
|
|
86
|
+
|
|
85
87
|
Large outputs are linked `hoi4-agent://` resources. For resources over 1 MiB, follow the `continuationUri` returned in `_meta` until it is `null`; clients may also request byte ranges with `?offset=<bytes>&length=<bytes>`.
|
|
86
88
|
|
|
87
89
|
## Coexistence with agent workflows
|
package/dist/bin/stdio.js
CHANGED
|
@@ -8,6 +8,10 @@ async function main() {
|
|
|
8
8
|
const engine = await createEngine();
|
|
9
9
|
const context = {};
|
|
10
10
|
const server = createMcpServer(engine, context);
|
|
11
|
+
if (process.env.HOI4_AGENT_TOOLS_CHAOSX === '1') {
|
|
12
|
+
const { registerChaosxTools } = await import('../hoi4_agent_tools/mcp/tools/chaosx.js');
|
|
13
|
+
registerChaosxTools(server, engine, context);
|
|
14
|
+
}
|
|
11
15
|
server.server.onerror = (error) => {
|
|
12
16
|
const fatalInputError = error instanceof StdioFrameLimitError || error instanceof StdioInvalidUtf8Error;
|
|
13
17
|
const rejectedMessage = error instanceof StdioInvalidMessageError;
|
|
@@ -58,6 +58,18 @@ export function probabilityDomainScanPatterns(workspace, sourcePaths = []) {
|
|
|
58
58
|
'events/**/*.txt',
|
|
59
59
|
]);
|
|
60
60
|
}
|
|
61
|
+
/** Text sources needed to discover country flags and leader portraits. */
|
|
62
|
+
export function countryAssetDiscoveryPatterns(workspace) {
|
|
63
|
+
const roots = workspace.registration.roots;
|
|
64
|
+
return uniquePatterns([
|
|
65
|
+
'history/countries/**/*.txt',
|
|
66
|
+
'common/characters/**/*.txt',
|
|
67
|
+
'common/scripted_effects/**/*.txt',
|
|
68
|
+
'events/**/*.txt',
|
|
69
|
+
...under(roots.interface, '**/*.gfx'),
|
|
70
|
+
...under(roots.gfx, '**/*.gfx'),
|
|
71
|
+
]);
|
|
72
|
+
}
|
|
61
73
|
export function exactChangedFilePatterns(relativePaths) {
|
|
62
74
|
return uniquePatterns(relativePaths.map((relativePath) => relativePath.replaceAll('\\', '/').replace(/^\.\//u, '')));
|
|
63
75
|
}
|
|
@@ -664,7 +664,7 @@ export class GuiAssetCatalog {
|
|
|
664
664
|
const pageFile = this.resolveBmFontPage(entry, pagePath ?? '', metric.page);
|
|
665
665
|
const raster = pageFile === undefined
|
|
666
666
|
? undefined
|
|
667
|
-
: await this.loadBmFontGlyph(entry, pageFile, metric, codePoint);
|
|
667
|
+
: await this.loadBmFontGlyph(entry, pageFile, metric, codePoint, scale);
|
|
668
668
|
if (raster !== undefined && pageFile !== undefined) {
|
|
669
669
|
pageHashes.add(pageFile.sha256);
|
|
670
670
|
glyphs.push({
|
|
@@ -674,8 +674,8 @@ export class GuiAssetCatalog {
|
|
|
674
674
|
...(raster.borderDataUri === undefined ? {} : { borderDataUri: raster.borderDataUri }),
|
|
675
675
|
x: penX + metric.xOffset * scale,
|
|
676
676
|
y: metric.yOffset * scale,
|
|
677
|
-
width: raster.width * scale,
|
|
678
|
-
height: raster.height * scale,
|
|
677
|
+
width: raster.width * (scale > 1 ? 1 : scale),
|
|
678
|
+
height: raster.height * (scale > 1 ? 1 : scale),
|
|
679
679
|
});
|
|
680
680
|
}
|
|
681
681
|
else if (metric.width > 0 &&
|
|
@@ -782,10 +782,13 @@ export class GuiAssetCatalog {
|
|
|
782
782
|
this.metrics.set(key, null);
|
|
783
783
|
return undefined;
|
|
784
784
|
}
|
|
785
|
-
loadBmFontGlyph(entry, pageFile, metric, codePoint) {
|
|
785
|
+
loadBmFontGlyph(entry, pageFile, metric, codePoint, scale) {
|
|
786
786
|
if (metric.width <= 0 || metric.height <= 0)
|
|
787
787
|
return Promise.resolve(undefined);
|
|
788
|
-
const
|
|
788
|
+
const rasterScale = scale > 1 ? scale : 1;
|
|
789
|
+
const targetWidth = Math.max(1, Math.round(metric.width * rasterScale));
|
|
790
|
+
const targetHeight = Math.max(1, Math.round(metric.height * rasterScale));
|
|
791
|
+
const key = `${entry.sourceFile.sha256}:${pageFile.sha256}:${codePoint}:${metric.x}:${metric.y}:${metric.width}:${metric.height}:${targetWidth}:${targetHeight}`;
|
|
789
792
|
let promise = this.glyphRasters.get(key);
|
|
790
793
|
if (promise === undefined) {
|
|
791
794
|
promise = (async () => {
|
|
@@ -799,7 +802,7 @@ export class GuiAssetCatalog {
|
|
|
799
802
|
metric.y + metric.height > raster.height)
|
|
800
803
|
return undefined;
|
|
801
804
|
this.budget.reserveRasterOperation(`gui-bmfont-glyph:${key}`, `GUI BMFont glyph rasterization U+${codePoint.toString(16).toUpperCase()}`);
|
|
802
|
-
this.budget.reserve(
|
|
805
|
+
this.budget.reserve(targetWidth, targetHeight, `GUI BMFont glyph U+${codePoint.toString(16).toUpperCase()}`, { maximumPixels: RENDER_MAX_DECODED_PIXELS });
|
|
803
806
|
const extracted = await sharp(raster.data, {
|
|
804
807
|
raw: { width: raster.width, height: raster.height, channels: 4 },
|
|
805
808
|
limitInputPixels: RENDER_MAX_DECODED_PIXELS,
|
|
@@ -919,12 +922,21 @@ export class GuiAssetCatalog {
|
|
|
919
922
|
}
|
|
920
923
|
}
|
|
921
924
|
}
|
|
922
|
-
const encodeMask = (pixels) =>
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
.
|
|
925
|
+
const encodeMask = (pixels) => {
|
|
926
|
+
let pipeline = sharp(pixels, {
|
|
927
|
+
raw: { width: extracted.info.width, height: extracted.info.height, channels: 4 },
|
|
928
|
+
limitInputPixels: RENDER_MAX_DECODED_PIXELS,
|
|
929
|
+
});
|
|
930
|
+
if (targetWidth !== extracted.info.width || targetHeight !== extracted.info.height) {
|
|
931
|
+
pipeline = pipeline.resize(targetWidth, targetHeight, {
|
|
932
|
+
fit: 'fill',
|
|
933
|
+
kernel: scale >= 1 ? sharp.kernel.nearest : sharp.kernel.lanczos3,
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
return pipeline
|
|
937
|
+
.png({ compressionLevel: 9, adaptiveFiltering: false, palette: false })
|
|
938
|
+
.toBuffer();
|
|
939
|
+
};
|
|
928
940
|
const png = await encodeMask(facePixels);
|
|
929
941
|
const borderPng = borderPixels === undefined ? undefined : await encodeMask(borderPixels);
|
|
930
942
|
return {
|
|
@@ -932,8 +944,8 @@ export class GuiAssetCatalog {
|
|
|
932
944
|
...(borderPng === undefined
|
|
933
945
|
? {}
|
|
934
946
|
: { borderDataUri: `data:image/png;base64,${borderPng.toString('base64')}` }),
|
|
935
|
-
width:
|
|
936
|
-
height:
|
|
947
|
+
width: targetWidth,
|
|
948
|
+
height: targetHeight,
|
|
937
949
|
};
|
|
938
950
|
})();
|
|
939
951
|
this.glyphRasters.set(key, promise);
|
|
@@ -60,22 +60,15 @@ function sceneGlyphDefinitions(scene) {
|
|
|
60
60
|
.map(([key, glyphPath]) => `<path id="${outlineDefinitionId(key)}" d="${glyphPath}"/>`),
|
|
61
61
|
...[...bitmaps.entries()]
|
|
62
62
|
.sort(([left], [right]) => compareCodeUnits(left, right))
|
|
63
|
-
.map(([key, glyph]) => `<image id="${bitmapDefinitionId(key)}" width="${finite(glyph.width)}" height="${finite(glyph.height)}" href="${glyph.dataUri}" preserveAspectRatio="none" image-rendering="optimizeSpeed"/>${glyph.borderDataUri === undefined ? '' : `<image id="${bitmapDefinitionId(key, 'border')}" width="${finite(glyph.width)}" height="${finite(glyph.height)}" href="${glyph.borderDataUri}" preserveAspectRatio="none" image-rendering="optimizeSpeed"/>`}`),
|
|
63
|
+
.map(([key, glyph]) => `<image id="${bitmapDefinitionId(key)}" width="${finite(glyph.width)}" height="${finite(glyph.height)}" href="${glyph.dataUri}" preserveAspectRatio="none" image-rendering="optimizeSpeed" style="image-rendering:pixelated"/>${glyph.borderDataUri === undefined ? '' : `<image id="${bitmapDefinitionId(key, 'border')}" width="${finite(glyph.width)}" height="${finite(glyph.height)}" href="${glyph.borderDataUri}" preserveAspectRatio="none" image-rendering="optimizeSpeed" style="image-rendering:pixelated"/>`}`),
|
|
64
64
|
].join('');
|
|
65
65
|
}
|
|
66
66
|
function bitmapTintMarkup(id, colour, glyphs, rect) {
|
|
67
67
|
const match = /^#([0-9a-f]{6})([0-9a-f]{2})?$/iu.exec(colour);
|
|
68
68
|
const rgb = match?.[1] ?? 'ffffff';
|
|
69
69
|
const alpha = match?.[2] ?? 'ff';
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
Number.parseInt(rgb.slice(2, 4), 16) / 255,
|
|
73
|
-
Number.parseInt(rgb.slice(4, 6), 16) / 255,
|
|
74
|
-
Number.parseInt(alpha, 16) / 255,
|
|
75
|
-
].map(finite);
|
|
76
|
-
const [red, green, blue, opacity] = channels;
|
|
77
|
-
const matrix = `${red} 0 0 0 0 0 ${green} 0 0 0 0 0 ${blue} 0 0 0 0 0 ${opacity} 0`;
|
|
78
|
-
return `<defs><filter id="${id}" filterUnits="userSpaceOnUse" ${rectAttributes(rect)} color-interpolation-filters="sRGB"><feColorMatrix type="matrix" values="${matrix}"/></filter></defs><g filter="url(#${id})">${glyphs}</g>`;
|
|
70
|
+
const opacity = finite(Number.parseInt(alpha, 16) / 255);
|
|
71
|
+
return `<defs><mask id="${id}" maskUnits="userSpaceOnUse" maskContentUnits="userSpaceOnUse" ${rectAttributes(rect)} style="mask-type:alpha"><g>${glyphs}</g></mask></defs><rect ${rectAttributes(rect)} fill="#${rgb}"${opacity >= 1 ? '' : ` fill-opacity="${opacity}"`} mask="url(#${id})"/>`;
|
|
79
72
|
}
|
|
80
73
|
function actualGlyphMarkup(glyphLine, originX, baseline, horizontalScale, bitmapLayer = 'face') {
|
|
81
74
|
if (glyphLine.source === 'fontkit-path')
|
|
@@ -92,7 +85,7 @@ function actualGlyphMarkup(glyphLine, originX, baseline, horizontalScale, bitmap
|
|
|
92
85
|
[glyph.x, glyph.y, glyph.width, glyph.height, glyphLine.baseline].every((value) => Math.abs(value - Math.round(value)) < 0.000_001);
|
|
93
86
|
const x = originX + glyph.x * horizontalScale;
|
|
94
87
|
const y = baseline + glyph.y - glyphLine.baseline;
|
|
95
|
-
return `<use href="#${bitmapDefinitionId(glyph.key, bitmapLayer)}" transform="translate(${finite(nativePixelGrid ? Math.round(x) : x)} ${finite(nativePixelGrid ? Math.round(y) : y)}) scale(${finite(horizontalScale)} 1)"/>`;
|
|
88
|
+
return `<use href="#${bitmapDefinitionId(glyph.key, bitmapLayer)}" transform="translate(${finite(nativePixelGrid ? Math.round(x) : x)} ${finite(nativePixelGrid ? Math.round(y) : y)}) scale(${finite(horizontalScale)} 1)" image-rendering="optimizeSpeed" style="image-rendering:pixelated"/>`;
|
|
96
89
|
})
|
|
97
90
|
.join('');
|
|
98
91
|
return '';
|
|
@@ -104,7 +104,13 @@ const GuiPreviewScenarioBodySchema = z
|
|
|
104
104
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
105
105
|
description: z.string().max(500).optional(),
|
|
106
106
|
resolution: GuiPreviewResolutionSchema.default({ width: 1920, height: 1080 }),
|
|
107
|
-
uiScale: z
|
|
107
|
+
uiScale: z
|
|
108
|
+
.number()
|
|
109
|
+
.positive()
|
|
110
|
+
.min(0.25)
|
|
111
|
+
.max(4)
|
|
112
|
+
.default(1)
|
|
113
|
+
.describe('In-game UI scale. Use 1 for the normal 1920x1080 layout; this changes layout geometry and is not an output-image zoom.'),
|
|
108
114
|
state: previewStateSchema.default('normal'),
|
|
109
115
|
language: z
|
|
110
116
|
.string()
|