hoi4-agent-tools 3.5.1 → 3.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 +7 -0
- package/README.md +43 -116
- package/dist/hoi4_agent_tools/gui/layout.js +237 -11
- package/dist/hoi4_agent_tools/gui/renderer.js +36 -3
- package/dist/hoi4_agent_tools/gui/scenario.js +18 -0
- package/dist/hoi4_agent_tools/gui/studio.js +73 -21
- package/dist/hoi4_agent_tools/mcp/server/instructions.js +1 -1
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/README.md +33 -18
- package/docs/examples.md +85 -0
- package/docs/gui.md +37 -0
- package/docs/setup.md +50 -26
- package/docs/tools.md +64 -0
- package/package.json +4 -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/decision-inspect-input.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 +139 -89
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.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/schemas/reference-context-input.schema.json +1 -1
- package/schemas/reference-read-input.schema.json +1 -1
- package/schemas/reference-search-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +1 -1
- package/schemas/scenario-test-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/schemas/source-lookup-input.schema.json +1 -1
- package/server.json +3 -3
|
@@ -39,7 +39,10 @@ function bitmapDefinitionId(key, layer = 'face') {
|
|
|
39
39
|
function sceneGlyphDefinitions(scene) {
|
|
40
40
|
const outlines = new Map();
|
|
41
41
|
const bitmaps = new Map();
|
|
42
|
-
for (const element of
|
|
42
|
+
for (const element of [
|
|
43
|
+
...scene.elements,
|
|
44
|
+
...(scene.tooltipPreview === undefined ? [] : [scene.tooltipPreview.textElement]),
|
|
45
|
+
]) {
|
|
43
46
|
for (const line of element.text?.glyphLines ?? []) {
|
|
44
47
|
for (const glyph of line.glyphs) {
|
|
45
48
|
if (glyph.kind === 'outline')
|
|
@@ -400,6 +403,8 @@ function renderOverlay(element, variant, toolText) {
|
|
|
400
403
|
if (rect.width <= 0 || rect.height <= 0)
|
|
401
404
|
return '';
|
|
402
405
|
if (variant === 'click-regions') {
|
|
406
|
+
if (element.enablement === 'unresolved')
|
|
407
|
+
return `<g><rect ${rectAttributes(rect)} fill="#ffb84d" fill-opacity="0.12" stroke="#ffb84d" stroke-width="2" stroke-dasharray="5 3"/><title>${escapeXml(`Potential click region: ${element.enablementReason ?? element.visibilityStatusReason ?? element.name}`)}</title>${toolText.render(`${element.name} (?)`, { x: rect.x + 3, y: rect.y + 13, fontSize: 11, fill: '#ffb84d' })}</g>`;
|
|
403
408
|
if (!element.clickable)
|
|
404
409
|
return '';
|
|
405
410
|
return `<g><rect ${rectAttributes(rect)} fill="#00d4ff" fill-opacity="0.2" stroke="#00d4ff" stroke-width="2"/>${toolText.render(element.name, { x: rect.x + 3, y: rect.y + 13, fontSize: 11, fill: '#00d4ff' })}</g>`;
|
|
@@ -418,6 +423,13 @@ function renderOverlay(element, variant, toolText) {
|
|
|
418
423
|
}
|
|
419
424
|
return '';
|
|
420
425
|
}
|
|
426
|
+
function renderTooltipPreview(scene, toolText) {
|
|
427
|
+
const tooltip = scene.tooltipPreview;
|
|
428
|
+
if (tooltip === undefined)
|
|
429
|
+
return '';
|
|
430
|
+
return (renderBaseElement(tooltip.element, undefined, toolText) +
|
|
431
|
+
renderBaseElement(tooltip.textElement, undefined, toolText));
|
|
432
|
+
}
|
|
421
433
|
function viewFor(scene, variant) {
|
|
422
434
|
if (variant !== 'cropped' || scene.bounds.width <= 0 || scene.bounds.height <= 0) {
|
|
423
435
|
return {
|
|
@@ -453,7 +465,7 @@ export function sceneToSvg(scene, variant) {
|
|
|
453
465
|
: [`<clipPath id="clip-${index}"><rect ${rectAttributes(element.clipRect)}/></clipPath>`]);
|
|
454
466
|
const body = scene.elements
|
|
455
467
|
.map((element, index) => renderBaseElement(element, element.clipRect === undefined ? undefined : `clip-${index}`, toolText))
|
|
456
|
-
.join('');
|
|
468
|
+
.join('') + renderTooltipPreview(scene, toolText);
|
|
457
469
|
const overlays = scene.elements
|
|
458
470
|
.map((element) => renderOverlay(element, variant, toolText))
|
|
459
471
|
.join('');
|
|
@@ -479,7 +491,7 @@ async function sceneToSvgCooperative(scene, variant, signal) {
|
|
|
479
491
|
const clipDefinitions = await cooperativeParts(scene.elements, (element, index) => element.clipRect === undefined
|
|
480
492
|
? ''
|
|
481
493
|
: `<clipPath id="clip-${index}"><rect ${rectAttributes(element.clipRect)}/></clipPath>`, signal);
|
|
482
|
-
const body = await cooperativeParts(scene.elements, (element, index) => renderBaseElement(element, element.clipRect === undefined ? undefined : `clip-${index}`, toolText), signal);
|
|
494
|
+
const body = (await cooperativeParts(scene.elements, (element, index) => renderBaseElement(element, element.clipRect === undefined ? undefined : `clip-${index}`, toolText), signal)) + renderTooltipPreview(scene, toolText);
|
|
483
495
|
const overlays = await cooperativeParts(scene.elements, (element) => renderOverlay(element, variant, toolText), signal);
|
|
484
496
|
const fidelity = escapeXml(canonicalJson(scene.fidelity));
|
|
485
497
|
return `<svg xmlns="http://www.w3.org/2000/svg" width="${view.width}" height="${view.height}" viewBox="${finite(view.viewBox.x)} ${finite(view.viewBox.y)} ${finite(view.viewBox.width)} ${finite(view.viewBox.height)}"><metadata data-renderer="hoi4-agent-tools" data-mode="offline">${fidelity}</metadata><defs>${clipDefinitions}${sceneGlyphDefinitions(scene)}${toolText.definitions()}</defs><rect ${rectAttributes(view.viewBox)} fill="#17202a"/>${body}${overlays}</svg>`;
|
|
@@ -526,6 +538,27 @@ export function hierarchyToSvg(scene) {
|
|
|
526
538
|
function sceneLayoutEvidence(scene) {
|
|
527
539
|
return {
|
|
528
540
|
...scene,
|
|
541
|
+
...(scene.tooltipPreview === undefined
|
|
542
|
+
? {}
|
|
543
|
+
: {
|
|
544
|
+
tooltipPreview: {
|
|
545
|
+
targetId: scene.tooltipPreview.targetId,
|
|
546
|
+
localisationKey: scene.tooltipPreview.localisationKey,
|
|
547
|
+
placement: scene.tooltipPreview.placement,
|
|
548
|
+
element: {
|
|
549
|
+
id: scene.tooltipPreview.element.id,
|
|
550
|
+
rect: scene.tooltipPreview.element.rect,
|
|
551
|
+
sprite: scene.tooltipPreview.element.sprite?.spriteName,
|
|
552
|
+
},
|
|
553
|
+
textElement: {
|
|
554
|
+
id: scene.tooltipPreview.textElement.id,
|
|
555
|
+
rect: scene.tooltipPreview.textElement.rect,
|
|
556
|
+
text: scene.tooltipPreview.textElement.text?.text,
|
|
557
|
+
lines: scene.tooltipPreview.textElement.text?.lines,
|
|
558
|
+
fontName: scene.tooltipPreview.textElement.text?.fontName,
|
|
559
|
+
},
|
|
560
|
+
},
|
|
561
|
+
}),
|
|
529
562
|
elements: scene.elements.map((element) => ({
|
|
530
563
|
...element,
|
|
531
564
|
...(element.sprite === undefined
|
|
@@ -103,6 +103,24 @@ const GuiPreviewScenarioBodySchema = z
|
|
|
103
103
|
.min(1)
|
|
104
104
|
.max(128)
|
|
105
105
|
.regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
106
|
+
tooltip: z
|
|
107
|
+
.object({
|
|
108
|
+
target: elementNameSchema,
|
|
109
|
+
x: z.number().nonnegative(),
|
|
110
|
+
y: z.number().nonnegative(),
|
|
111
|
+
width: z.number().positive().max(RENDER_MAX_DIMENSION),
|
|
112
|
+
padding: z
|
|
113
|
+
.number()
|
|
114
|
+
.nonnegative()
|
|
115
|
+
.max(RENDER_MAX_DIMENSION / 2)
|
|
116
|
+
.describe('Caller-declared inset on each side, in viewport pixels; no native padding is inferred.'),
|
|
117
|
+
})
|
|
118
|
+
.strict()
|
|
119
|
+
.refine(({ padding, width }) => padding * 2 < width, {
|
|
120
|
+
path: ['padding'],
|
|
121
|
+
message: 'Tooltip padding must leave a positive text width.',
|
|
122
|
+
})
|
|
123
|
+
.optional(),
|
|
106
124
|
description: z.string().max(500).optional(),
|
|
107
125
|
date: GuiScenarioDateSchema.optional(),
|
|
108
126
|
controls: ConditionControlMapSchema.optional(),
|
|
@@ -343,14 +343,20 @@ function localisationIconSpriteCandidates(token) {
|
|
|
343
343
|
const normalized = token.startsWith('GFX_') ? token : `GFX_${token}`;
|
|
344
344
|
return [...new Set([normalized, `${normalized}_texticon`, `${normalized}_text_icon`])];
|
|
345
345
|
}
|
|
346
|
-
export function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNames = [], languages = ['l_english'], additionalTemplateNames = []) {
|
|
346
|
+
export function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNames = [], languages = ['l_english'], additionalTemplateNames = [], includeImmediateTooltip = false) {
|
|
347
347
|
const spriteNames = new Set(additionalSpriteNames);
|
|
348
348
|
const fontNames = new Set();
|
|
349
349
|
const textValues = new Set();
|
|
350
|
+
if (includeImmediateTooltip) {
|
|
351
|
+
spriteNames.add('ToolTip');
|
|
352
|
+
fontNames.add('cg_16b');
|
|
353
|
+
}
|
|
350
354
|
for (const element of selectedElements(graph, windowName, additionalTemplateNames)) {
|
|
351
355
|
collectNamedAttributes(element.attributes, new Set(['spriteType', 'quadTextureSprite']), spriteNames);
|
|
352
356
|
collectNamedAttributes(element.attributes, new Set(['font', 'buttonFont']), fontNames);
|
|
353
357
|
collectNamedAttributes(element.attributes, new Set(['text', 'buttonText', 'context_aware_text']), textValues);
|
|
358
|
+
if (includeImmediateTooltip)
|
|
359
|
+
collectNamedAttributes(element.attributes, new Set(['pdx_tooltip']), textValues);
|
|
354
360
|
}
|
|
355
361
|
for (const localisation of graph.localisation) {
|
|
356
362
|
if (textValues.has(localisation.key))
|
|
@@ -619,7 +625,7 @@ function assertUniqueScenarioIds(scenarios) {
|
|
|
619
625
|
seen.add(scenario.id);
|
|
620
626
|
}
|
|
621
627
|
}
|
|
622
|
-
function scenarioMatrixEvidence(graph, scenes) {
|
|
628
|
+
export function scenarioMatrixEvidence(graph, scenes) {
|
|
623
629
|
const primary = scenes[0];
|
|
624
630
|
if (primary === undefined)
|
|
625
631
|
return { offline: true, scenarios: [], changes: [] };
|
|
@@ -664,16 +670,23 @@ function scenarioMatrixEvidence(graph, scenes) {
|
|
|
664
670
|
if (matches.length === 0)
|
|
665
671
|
return [];
|
|
666
672
|
const shown = matches
|
|
667
|
-
.filter(({ elements }) => elements.
|
|
673
|
+
.filter(({ elements }) => elements.every(({ visibilityStatus }) => visibilityStatus !== 'unresolved') &&
|
|
674
|
+
elements.some(({ visibilityStatus }) => visibilityStatus === 'shown'))
|
|
668
675
|
.map(({ scene }) => scene.scenario.id);
|
|
669
676
|
const hidden = matches
|
|
670
|
-
.filter(({ elements }) => elements.every(({
|
|
677
|
+
.filter(({ elements }) => elements.every(({ visibilityStatus }) => visibilityStatus === 'hidden'))
|
|
678
|
+
.map(({ scene }) => scene.scenario.id);
|
|
679
|
+
const unresolvedVisibility = matches
|
|
680
|
+
.filter(({ elements }) => elements.some(({ visibilityStatus }) => visibilityStatus === 'unresolved'))
|
|
671
681
|
.map(({ scene }) => scene.scenario.id);
|
|
672
682
|
const enabled = matches
|
|
673
|
-
.filter(({ elements }) => elements.some(({
|
|
683
|
+
.filter(({ elements }) => elements.some(({ enablement }) => enablement === 'enabled'))
|
|
674
684
|
.map(({ scene }) => scene.scenario.id);
|
|
675
685
|
const disabled = matches
|
|
676
|
-
.filter(({ elements }) => elements.every(({
|
|
686
|
+
.filter(({ elements }) => elements.every(({ enablement }) => enablement === 'disabled'))
|
|
687
|
+
.map(({ scene }) => scene.scenario.id);
|
|
688
|
+
const unresolved = matches
|
|
689
|
+
.filter(({ elements }) => elements.some(({ enablement }) => enablement === 'unresolved'))
|
|
677
690
|
.map(({ scene }) => scene.scenario.id);
|
|
678
691
|
return [
|
|
679
692
|
{
|
|
@@ -682,10 +695,24 @@ function scenarioMatrixEvidence(graph, scenes) {
|
|
|
682
695
|
...new Set(matches.flatMap(({ elements }) => elements.map(({ sourcePath }) => sourcePath))),
|
|
683
696
|
].sort(compareCodeUnits),
|
|
684
697
|
...(visibilityNames.has(name)
|
|
685
|
-
? {
|
|
698
|
+
? {
|
|
699
|
+
visibility: {
|
|
700
|
+
shown,
|
|
701
|
+
hidden,
|
|
702
|
+
unresolved: unresolvedVisibility,
|
|
703
|
+
covered: shown.length > 0 && hidden.length > 0,
|
|
704
|
+
},
|
|
705
|
+
}
|
|
686
706
|
: {}),
|
|
687
707
|
...(enabledNames.has(name)
|
|
688
|
-
? {
|
|
708
|
+
? {
|
|
709
|
+
enabled: {
|
|
710
|
+
enabled,
|
|
711
|
+
disabled,
|
|
712
|
+
unresolved,
|
|
713
|
+
covered: enabled.length > 0 && disabled.length > 0,
|
|
714
|
+
},
|
|
715
|
+
}
|
|
689
716
|
: {}),
|
|
690
717
|
},
|
|
691
718
|
];
|
|
@@ -736,13 +763,34 @@ function scenarioMatrixEvidence(graph, scenes) {
|
|
|
736
763
|
})),
|
|
737
764
|
visibleElements: scene.elements
|
|
738
765
|
.filter(({ visible }) => visible)
|
|
739
|
-
.map(({ id, name }) => ({ id, name })),
|
|
766
|
+
.map(({ id, name, visibilityStatus }) => ({ id, name, visibilityStatus })),
|
|
740
767
|
hiddenElements: scene.elements
|
|
741
768
|
.filter(({ visible }) => !visible)
|
|
742
|
-
.map(({ id, name, visibilityReason }) => ({
|
|
769
|
+
.map(({ id, name, visibilityReason, visibilityStatus }) => ({
|
|
770
|
+
id,
|
|
771
|
+
name,
|
|
772
|
+
reason: visibilityReason,
|
|
773
|
+
visibilityStatus,
|
|
774
|
+
})),
|
|
775
|
+
unresolvedVisibility: scene.elements
|
|
776
|
+
.filter(({ visibilityStatus }) => visibilityStatus === 'unresolved')
|
|
777
|
+
.map(({ id, name, visible, visibilityStatusReason }) => ({
|
|
778
|
+
id,
|
|
779
|
+
name,
|
|
780
|
+
previewVisible: visible,
|
|
781
|
+
reason: visibilityStatusReason,
|
|
782
|
+
})),
|
|
743
783
|
disabledElements: scene.elements
|
|
744
|
-
.filter(({ disabledReason }) => disabledReason !== undefined)
|
|
784
|
+
.filter(({ disabledReason, enablement }) => disabledReason !== undefined && enablement === 'disabled')
|
|
745
785
|
.map(({ id, name, disabledReason }) => ({ id, name, reason: disabledReason })),
|
|
786
|
+
unresolvedClickRegions: scene.elements
|
|
787
|
+
.filter(({ visible, enablement }) => visible && enablement === 'unresolved')
|
|
788
|
+
.map(({ id, name, rect, enablementReason }) => ({
|
|
789
|
+
id,
|
|
790
|
+
name,
|
|
791
|
+
rect,
|
|
792
|
+
reason: enablementReason,
|
|
793
|
+
})),
|
|
746
794
|
})),
|
|
747
795
|
changes: scenes.slice(1).map((scene) => {
|
|
748
796
|
const currentByKey = new Map(scene.elements.map((element) => [elementKey(element), element]));
|
|
@@ -758,13 +806,13 @@ function scenarioMatrixEvidence(graph, scenes) {
|
|
|
758
806
|
for (const key of keys) {
|
|
759
807
|
const before = primaryByKey.get(key);
|
|
760
808
|
const after = currentByKey.get(key);
|
|
761
|
-
if (before?.
|
|
809
|
+
if (before?.visibilityStatus === 'hidden' && after?.visibilityStatus === 'shown')
|
|
762
810
|
shown.push(after.name);
|
|
763
|
-
if (before?.
|
|
811
|
+
if (before?.visibilityStatus === 'shown' && after?.visibilityStatus === 'hidden')
|
|
764
812
|
hidden.push(before.name);
|
|
765
|
-
if (before?.
|
|
813
|
+
if (before?.enablement === 'enabled' && after?.enablement === 'disabled')
|
|
766
814
|
disabled.push(after.name);
|
|
767
|
-
if (before?.
|
|
815
|
+
if (before?.enablement === 'disabled' && after?.enablement === 'enabled')
|
|
768
816
|
enabled.push(after.name);
|
|
769
817
|
if (before === undefined || after === undefined)
|
|
770
818
|
continue;
|
|
@@ -924,7 +972,7 @@ export class ScriptedGuiStudio {
|
|
|
924
972
|
const files = mergeScannedFiles(snapshot.files, localisationFiles);
|
|
925
973
|
return this.graphForFiles(files, `workspace:${workspaceId}`);
|
|
926
974
|
}
|
|
927
|
-
async scanWindow(workspaceId, windowName, principal, signal, languages = ['l_english'], additionalSpriteNames = [], additionalTemplateNames = [], additionalAssetPatterns = [], additionalLocalisationKeys = []) {
|
|
975
|
+
async scanWindow(workspaceId, windowName, principal, signal, languages = ['l_english'], additionalSpriteNames = [], additionalTemplateNames = [], additionalAssetPatterns = [], additionalLocalisationKeys = [], includeImmediateTooltip = false) {
|
|
928
976
|
const workspace = this.resolver.get(workspaceId, principal);
|
|
929
977
|
const layoutPatterns = guiLayoutPatterns(workspace);
|
|
930
978
|
const overlayLayouts = await this.engine.scan(workspaceId, {
|
|
@@ -955,7 +1003,7 @@ export class ScriptedGuiStudio {
|
|
|
955
1003
|
const definitions = this.graphForFiles(definitionFiles, `definitions:${workspaceId}`, additionalLocalisationKeys);
|
|
956
1004
|
assertTargetWindowAvailable(definitions.graph, windowName);
|
|
957
1005
|
const referencedPatterns = [
|
|
958
|
-
...referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames, languages, additionalTemplateNames),
|
|
1006
|
+
...referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames, languages, additionalTemplateNames, includeImmediateTooltip),
|
|
959
1007
|
...additionalAssetPatterns,
|
|
960
1008
|
];
|
|
961
1009
|
const exactReferenced = referencedPatterns.length === 0
|
|
@@ -1000,7 +1048,7 @@ export class ScriptedGuiStudio {
|
|
|
1000
1048
|
const generatedOptions = input.generatedScenarios === undefined
|
|
1001
1049
|
? undefined
|
|
1002
1050
|
: parseGeneratedScenarioOptions(input.generatedScenarios);
|
|
1003
|
-
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [placeholderScenario.language, ...explicitRelatedScenarios.map(({ language }) => language)], scenarioSpriteNames([placeholderScenario, ...explicitRelatedScenarios], generatedOptions?.textSamples), scenarioTemplateNames([placeholderScenario, ...explicitRelatedScenarios]), scenarioCountryFlagPatterns([placeholderScenario, ...explicitRelatedScenarios]), scenarioLocalisationKeys([placeholderScenario, ...explicitRelatedScenarios]));
|
|
1051
|
+
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [placeholderScenario.language, ...explicitRelatedScenarios.map(({ language }) => language)], scenarioSpriteNames([placeholderScenario, ...explicitRelatedScenarios], generatedOptions?.textSamples), scenarioTemplateNames([placeholderScenario, ...explicitRelatedScenarios]), scenarioCountryFlagPatterns([placeholderScenario, ...explicitRelatedScenarios]), scenarioLocalisationKeys([placeholderScenario, ...explicitRelatedScenarios]), [placeholderScenario, ...explicitRelatedScenarios].some(({ tooltip }) => tooltip !== undefined));
|
|
1004
1052
|
const generatedScenarios = generatedOptions === undefined
|
|
1005
1053
|
? []
|
|
1006
1054
|
: generateGuiPreviewScenarios(scanned.graph, input.windowName, placeholderScenario, generatedOptions, ClausewitzEvaluationDefinitions.build(scanned));
|
|
@@ -1030,7 +1078,7 @@ export class ScriptedGuiStudio {
|
|
|
1030
1078
|
const budget = new RenderBudget();
|
|
1031
1079
|
const beforeScenario = parsePreviewScenario(input.before);
|
|
1032
1080
|
const afterScenario = parsePreviewScenario(input.after);
|
|
1033
|
-
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [beforeScenario.language, afterScenario.language], scenarioSpriteNames([beforeScenario, afterScenario]), scenarioTemplateNames([beforeScenario, afterScenario]), scenarioCountryFlagPatterns([beforeScenario, afterScenario]), scenarioLocalisationKeys([beforeScenario, afterScenario]));
|
|
1081
|
+
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [beforeScenario.language, afterScenario.language], scenarioSpriteNames([beforeScenario, afterScenario]), scenarioTemplateNames([beforeScenario, afterScenario]), scenarioCountryFlagPatterns([beforeScenario, afterScenario]), scenarioLocalisationKeys([beforeScenario, afterScenario]), [beforeScenario, afterScenario].some(({ tooltip }) => tooltip !== undefined));
|
|
1034
1082
|
const beforeCatalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, beforeScenario.language);
|
|
1035
1083
|
const afterCatalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, afterScenario.language);
|
|
1036
1084
|
const before = await buildGuiScene(scanned.graph, scanned.files, input.windowName, beforeScenario, beforeCatalog);
|
|
@@ -1091,7 +1139,11 @@ export class ScriptedGuiStudio {
|
|
|
1091
1139
|
placeholderScenario,
|
|
1092
1140
|
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
1093
1141
|
...explicitRelatedScenarios,
|
|
1094
|
-
])
|
|
1142
|
+
]), [
|
|
1143
|
+
placeholderScenario,
|
|
1144
|
+
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
1145
|
+
...explicitRelatedScenarios,
|
|
1146
|
+
].some(({ tooltip }) => tooltip !== undefined));
|
|
1095
1147
|
const generatedScenarios = generatedOptions === undefined
|
|
1096
1148
|
? []
|
|
1097
1149
|
: generateGuiPreviewScenarios(scanned.graph, input.windowName, placeholderScenario, generatedOptions, ClausewitzEvaluationDefinitions.build(scanned));
|
|
@@ -1438,7 +1490,7 @@ export class ScriptedGuiStudio {
|
|
|
1438
1490
|
const previewScenario = input.scenario === undefined ? undefined : parsePreviewScenario(input.scenario);
|
|
1439
1491
|
const initiallyScanned = input.windowName === undefined
|
|
1440
1492
|
? await this.scan(input.workspaceId, input.principal, input.signal)
|
|
1441
|
-
: await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [previewScenario.language], scenarioSpriteNames([previewScenario]), scenarioTemplateNames([previewScenario]), scenarioCountryFlagPatterns([previewScenario]), scenarioLocalisationKeys([previewScenario]));
|
|
1493
|
+
: await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [previewScenario.language], scenarioSpriteNames([previewScenario]), scenarioTemplateNames([previewScenario]), scenarioCountryFlagPatterns([previewScenario]), scenarioLocalisationKeys([previewScenario]), previewScenario.tooltip !== undefined);
|
|
1442
1494
|
const exactTargets = await this.scanner.scan(workspace, {
|
|
1443
1495
|
patterns: prepared.map(({ relativePath }) => relativePath),
|
|
1444
1496
|
...(input.signal === undefined ? {} : { signal: input.signal }),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** Shared agent-facing guidance for both MCP protocol generations. */
|
|
2
|
-
export const SERVER_INSTRUCTIONS = '
|
|
2
|
+
export const SERVER_INSTRUCTIONS = 'Start with the matching inspect tool; use hoi4.probability_inspect for weights and timing. Find local documentation with hoi4.reference_context, hoi4.reference_search and hoi4.reference_read, and source definitions with hoi4.source_lookup. Inspection and rendering are read-only; rewrite tools edit mod source. Results describe offline source and scenario models, not observed game behavior. Unresolved states are not confirmed coverage. Read linked artifacts when summaries omit detail. Long operations support negotiated MCP tasks and hoi4.job_inspect/hoi4.job_cancel; background rewrites need a stable requestKey.';
|
|
@@ -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.
|
|
3
|
+
export const PACKAGE_VERSION = '3.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/README.md
CHANGED
|
@@ -1,20 +1,35 @@
|
|
|
1
1
|
# Documentation
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
3
|
+
Start with [Setup](setup.md), then try the [Examples](examples.md).
|
|
4
|
+
You can ask your coding agent for these tasks in ordinary language; you do not need to memorize tool names.
|
|
5
|
+
|
|
6
|
+
## Choose a task
|
|
7
|
+
|
|
8
|
+
| I want to… | Read |
|
|
9
|
+
| ---------------------------------------------------- | ----------------------------------------- |
|
|
10
|
+
| Find a modding rule or a vanilla example | [Local references](reference.md) |
|
|
11
|
+
| Work on a focus tree | [Focus trees](focus.md) |
|
|
12
|
+
| Understand an event chain | [Events](events.md) |
|
|
13
|
+
| Trace dependencies or inspect decisions and missions | [Decisions and dependencies](analysis.md) |
|
|
14
|
+
| Preview a technology or doctrine tree | [Technology trees](technology.md) |
|
|
15
|
+
| Build or review an interface | [Scripted GUIs](gui.md) |
|
|
16
|
+
| Inspect or edit the map | [Maps](map.md) |
|
|
17
|
+
| Compare weights, chances or timing | [AI and probability](probability.md) |
|
|
18
|
+
| Test a mechanic or check a content package | [Mechanic tests](mechanics.md) |
|
|
19
|
+
|
|
20
|
+
## See the results
|
|
21
|
+
|
|
22
|
+
- [Examples](examples.md): practical prompts with real renders.
|
|
23
|
+
- [Focus and technology comparisons](visual-comparisons.md): supplied game captures beside source renders.
|
|
24
|
+
- [Interface comparisons](gui-comparisons.md): vanilla and Chaos Redux windows.
|
|
25
|
+
|
|
26
|
+
## Detailed reference
|
|
27
|
+
|
|
28
|
+
- [Tool reference](tools.md): tool names and what each one does.
|
|
29
|
+
- [Background jobs](jobs.md): inspect, cancel or retrieve long operations.
|
|
30
|
+
- [Large event and technology queries](helper-expansion.md): follow bounded results without losing coverage.
|
|
31
|
+
- [Remote hosting](http.md): share the server over HTTP.
|
|
32
|
+
- [Development](development.md): build, test and contribute.
|
|
33
|
+
- [Architecture decisions](adr/README.md): implementation rationale for contributors.
|
|
34
|
+
|
|
35
|
+
For vulnerability reporting, use the [security policy](../SECURITY.md).
|
package/docs/examples.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Examples
|
|
2
|
+
|
|
3
|
+
Start with a task you would normally give your coding agent.
|
|
4
|
+
The agent chooses the MCP tools and opens the resulting images or source references.
|
|
5
|
+
The renders below use actual Chaos Redux and installed HOI4 files; use names from your own mod when trying the prompts.
|
|
6
|
+
|
|
7
|
+
## Find a rule without reading a whole wiki
|
|
8
|
+
|
|
9
|
+
> Find the installed documentation for `save_event_target_as`, read the relevant section, and show a vanilla usage. Keep the file and line references.
|
|
10
|
+
|
|
11
|
+
The reference tools find the section, read a small excerpt, and retain its source revision so the agent can return to it later.
|
|
12
|
+
In the [measured reference example](research/reference-benchmark.md), the search found the installed effect documentation first and a 12-line read supplied the command and supported scopes.
|
|
13
|
+
No wiki download or game launch was needed.
|
|
14
|
+
|
|
15
|
+
Use [Local references](reference.md) to configure an offline wiki snapshot or narrow a search.
|
|
16
|
+
|
|
17
|
+
## Check alternative focus prerequisites
|
|
18
|
+
|
|
19
|
+
> Find Fury's focus tree. Render the branch leading to “The Last Neighbor Has Fallen” and show whether both preceding focuses are required or either one is enough.
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
Dotted connectors mark alternatives within one prerequisite group; solid connectors mark separate required prerequisites.
|
|
24
|
+
The agent can link each finding to the source block before you change the route.
|
|
25
|
+
The [in-game comparison](visual-comparisons.md#focus-trees) shows the same branch and explains the presentation differences.
|
|
26
|
+
|
|
27
|
+
## Review a large tree
|
|
28
|
+
|
|
29
|
+
> Render the Utopia Manifesto focus tree. Check its branch connections, alternative prerequisites, missing references and crowded areas before suggesting edits.
|
|
30
|
+
|
|
31
|
+
[](images/comparisons/utopia-mcp.png)
|
|
32
|
+
|
|
33
|
+
Open the image at full size for the complete layout.
|
|
34
|
+
This example contains 124 focuses in the recorded source version.
|
|
35
|
+
The [focus guide](focus.md) covers inspecting, rendering and reorganizing trees.
|
|
36
|
+
|
|
37
|
+
## Preview technology layouts and artwork
|
|
38
|
+
|
|
39
|
+
> Render the infantry technology folder using this mod and its installed game assets. Check card sizes, icons, prerequisite links and missing placements.
|
|
40
|
+
|
|
41
|
+
[](images/comparisons/infantry-mcp.png)
|
|
42
|
+
|
|
43
|
+
The preview follows the loaded technology and GUI definitions, including wide equipment cards.
|
|
44
|
+
The [technology comparisons](visual-comparisons.md#technology-folders) also show chemical and biological folders and explain why different source versions can produce different layouts.
|
|
45
|
+
See the [technology guide](technology.md) for grants, bonuses and doctrine paths.
|
|
46
|
+
|
|
47
|
+
## Inspect an interface before editing it
|
|
48
|
+
|
|
49
|
+
> Find the Video options window in the installed interface files. Render it with labelled control regions, then check its text, spacing and clipping.
|
|
50
|
+
|
|
51
|
+
| Supplied in-game capture | MCP source preview |
|
|
52
|
+
| ------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
|
|
53
|
+
|  |  |
|
|
54
|
+
|
|
55
|
+
For a mod window, ask for the same view at 100% and 125% UI scale and supply the state values needed by its controls.
|
|
56
|
+
Unknown state values must stay unresolved; a visible button alone does not establish that it is enabled in the game.
|
|
57
|
+
The [interface gallery](gui-comparisons.md) includes Škoda Priority, Chaos Redux Settings, Event Log, Chaos Meter, decisions and occupation views.
|
|
58
|
+
|
|
59
|
+
## Locate map data and plan an edit
|
|
60
|
+
|
|
61
|
+
> Find Brandenburg by name, show it on the map, and inspect its provinces, neighbours and railway connections. Show the affected files before changing anything.
|
|
62
|
+
|
|
63
|
+
[](images/comparisons/source/map-mcp.png)
|
|
64
|
+
|
|
65
|
+
After reviewing the selection, you can ask for a specific change such as moving named provinces into a state or updating a railway path.
|
|
66
|
+
The map tools check connected records and preserve dated state history when the operation can be represented safely.
|
|
67
|
+
See [Maps](map.md) for supported edits.
|
|
68
|
+
|
|
69
|
+
## Trace an event or compare AI weights
|
|
70
|
+
|
|
71
|
+
> Trace this event's options and follow-up events. Point out unresolved calls and link each route to its source.
|
|
72
|
+
|
|
73
|
+
[](images/comparisons/source/event-chain-mcp.png)
|
|
74
|
+
|
|
75
|
+
> Compare this decision's `ai_will_do` score before and after my change in peace and war. List missing conditions separately from known results.
|
|
76
|
+
|
|
77
|
+
Use [Events](events.md) for route graphs and [AI analysis](probability.md) for supported weights, chances and timing models.
|
|
78
|
+
A decision score is not automatically a percentage chance of an AI click.
|
|
79
|
+
|
|
80
|
+
## About these examples
|
|
81
|
+
|
|
82
|
+
The images are source previews, not recordings of the server running the game.
|
|
83
|
+
Supplied game screenshots are identified separately and published with permission.
|
|
84
|
+
The [comparison pages](visual-comparisons.md) and [interface gallery](gui-comparisons.md) keep source details, reproducible inputs and known differences alongside the images.
|
|
85
|
+
The package does not include HOI4 or Chaos Redux source files or artwork.
|
package/docs/gui.md
CHANGED
|
@@ -2,6 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
Use `hoi4.gui_inspect`, `hoi4.gui_render`, and `hoi4.gui_rewrite` for `.gui`, `.gfx`, `common/scripted_guis`, localisation, sprites, fonts, and linked decision entry points.
|
|
4
4
|
|
|
5
|
+
## Start with a preview
|
|
6
|
+
|
|
7
|
+
> Render this window at 100% and 125% UI scale. Show its control regions and list the state inputs needed to establish visibility and availability.
|
|
8
|
+
|
|
9
|
+
The [interface examples](gui-comparisons.md) show real vanilla and Chaos Redux windows.
|
|
10
|
+
Cyan click regions have confirmed availability under the supplied scenario.
|
|
11
|
+
Dashed amber regions are potential controls with unresolved visibility or enabling conditions; they do not count as confirmed branch coverage.
|
|
12
|
+
The scene retains the reason so the agent can supply the missing facts instead of guessing.
|
|
13
|
+
These are source-model results, not proof of native click execution.
|
|
14
|
+
|
|
15
|
+
## Preview immediate tooltip text
|
|
16
|
+
|
|
17
|
+
An explicit tooltip request uses a visible element's `pdx_tooltip` localisation key:
|
|
18
|
+
|
|
19
|
+
```json
|
|
20
|
+
{
|
|
21
|
+
"id": "close-tooltip",
|
|
22
|
+
"tooltip": {
|
|
23
|
+
"target": "close_button",
|
|
24
|
+
"x": 980,
|
|
25
|
+
"y": 100,
|
|
26
|
+
"width": 160,
|
|
27
|
+
"padding": 16
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The target can be an instance ID from inspection or a unique element name.
|
|
33
|
+
Position, width and padding are declared viewport pixels; height follows the measured text.
|
|
34
|
+
The preview uses the loaded `ToolTip` sprite and `cg_16b` font.
|
|
35
|
+
Missing or ambiguous targets, hidden or unresolved visibility, missing text/assets, and bounds errors produce diagnostics without a guessed popup.
|
|
36
|
+
A visibly disabled control can still have tooltip text.
|
|
37
|
+
|
|
38
|
+
Engine-generated `[!element_action]` and `[!element_action_enabled]` descriptions are not invented.
|
|
39
|
+
If their exact text is known, supply the exact bracketed token as a string key in `scenario.values` or `scenario.scriptedGui`; otherwise the tooltip remains unresolved.
|
|
40
|
+
The preview does not establish native automatic positioning, frame composition, delayed timing or `click_to_front` behavior.
|
|
41
|
+
|
|
5
42
|
## Create a GUI
|
|
6
43
|
|
|
7
44
|
`hoi4.gui_rewrite` can work from supported source, targeted patches, or structured helpers. Source and helper rewrites may include one bounded text package: the main `.gui` plus additional interface `.gui`/`.gfx`, configured GFX `.gfx`, `common/scripted_guis/*.txt`, and localisation `.yml` files. The package is path-checked, parsed, linked, rendered, validated, and applied together in one rewrite. Existing text encodings are retained, while localisation is always written as UTF-8 with BOM.
|
package/docs/setup.md
CHANGED
|
@@ -1,50 +1,74 @@
|
|
|
1
1
|
# Setup
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
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).
|
|
3
|
+
You need a coding agent that supports MCP and Node.js 22.19+ in the Node 22 line, or Node.js 24.
|
|
4
|
+
Keep an installed copy of Hearts of Iron IV available for vanilla references, artwork and fonts.
|
|
6
5
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Install
|
|
6
|
+
## 1. Install
|
|
10
7
|
|
|
11
8
|
```bash
|
|
12
9
|
npm install --global hoi4-agent-tools
|
|
13
10
|
```
|
|
14
11
|
|
|
15
|
-
|
|
12
|
+
Source inspection can work without an installed game.
|
|
13
|
+
Previews need the assets and fonts referenced by the mod; missing files are reported in the result.
|
|
14
|
+
|
|
15
|
+
## 2. Connect your agent
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
hoi4-agent-tools-setup --print-client-config
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Copy the printed `globalInstall` configuration into your MCP client, or use `codexTomlGlobal` for a TOML-based Codex configuration.
|
|
22
|
+
The printed command is correct for your platform.
|
|
23
|
+
|
|
24
|
+
Open the mod as your agent's workspace.
|
|
25
|
+
If your client does not supply workspace roots, set the MCP process working directory to that mod folder.
|
|
26
|
+
The server can then select the active mod without a separate configuration file.
|
|
27
|
+
It keeps indexes and generated images in your user data directory.
|
|
16
28
|
|
|
17
|
-
|
|
29
|
+
## 3. Try a small task
|
|
18
30
|
|
|
19
|
-
|
|
31
|
+
> Find the focus trees in this mod and render one of them.
|
|
20
32
|
|
|
21
|
-
|
|
33
|
+
> Find the installed documentation for `save_event_target_as` and read only the relevant section.
|
|
22
34
|
|
|
23
|
-
|
|
35
|
+
Continue with the [examples guide](examples.md).
|
|
36
|
+
A mod normally has `descriptor.mod` or standard HOI4 content folders; an empty folder needs a descriptor or explicit configuration.
|
|
24
37
|
|
|
25
|
-
|
|
38
|
+
## Choose game and mod paths
|
|
26
39
|
|
|
27
|
-
Use explicit paths
|
|
40
|
+
Use explicit paths if automatic discovery cannot find the game, or if you want to configure several mods:
|
|
28
41
|
|
|
29
42
|
```bash
|
|
30
|
-
hoi4-agent-tools-setup --init --mod-root /projects/hoi4-mods --
|
|
43
|
+
hoi4-agent-tools-setup --init --mod-root "/projects/hoi4-mods" --game-root "/games/Hearts of Iron IV"
|
|
44
|
+
hoi4-agent-tools-setup --print-client-config
|
|
31
45
|
```
|
|
32
46
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
47
|
+
`--mod-root` is a parent folder containing mod directories, not the game installation.
|
|
48
|
+
It can be repeated for several locations.
|
|
49
|
+
`--game-root` points to the installed game.
|
|
50
|
+
Use quoted Windows paths on Windows, for example `"C:/Program Files (x86)/Steam/steamapps/common/Hearts of Iron IV"`.
|
|
37
51
|
|
|
38
|
-
|
|
52
|
+
For a custom configuration location, add `--config PATH` to both setup commands.
|
|
53
|
+
`--workspace-storage-root PATH` chooses where generated indexes and images are saved.
|
|
54
|
+
Linked mod directories are not followed; configure their real parent directory instead.
|
|
39
55
|
|
|
40
|
-
|
|
56
|
+
## Add local reference material
|
|
41
57
|
|
|
42
|
-
|
|
58
|
+
Place an offline wiki snapshot in `paradox_wiki/` inside the mod.
|
|
59
|
+
An optional generated documentation dump can go in `script_docs/`.
|
|
60
|
+
For a configured workspace, `wikiRoot` and `scriptDocsRoot` can point to other absolute directories.
|
|
61
|
+
The server reads these local files; it does not download the wiki.
|
|
62
|
+
See [Local references](reference.md) for source selection and missing-source reports.
|
|
43
63
|
|
|
44
|
-
##
|
|
64
|
+
## Common problems
|
|
45
65
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
66
|
+
| Problem | Check |
|
|
67
|
+
| ----------------------------------- | --------------------------------------------------------------------------------------------------------------------------- |
|
|
68
|
+
| The agent cannot find a mod | Open the mod workspace, set the server working directory, or configure its real parent with `--mod-root`. |
|
|
69
|
+
| A render lacks icons or fonts | Confirm `--game-root` and the mod's asset paths. Read the missing-file findings. |
|
|
70
|
+
| A control's state is unresolved | Supply the scenario's required country, state, controller or variable inputs; do not assume a visible control is available. |
|
|
71
|
+
| A large query takes several minutes | Start with the specific tree, window or source area you need. Use [background jobs](jobs.md) when supported by your client. |
|
|
49
72
|
|
|
50
|
-
|
|
73
|
+
The supplied Codex registration allows 120 seconds for startup and 600 seconds per tool call.
|
|
74
|
+
For remote or shared deployments, use the [HTTP guide](http.md).
|