hoi4-agent-tools 2.8.4 → 2.8.6
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 +11 -0
- package/README.md +25 -25
- package/dist/hoi4_agent_tools/core/artifacts.js +14 -6
- package/dist/hoi4_agent_tools/gui/assets.js +9 -1
- package/dist/hoi4_agent_tools/gui/index.js +1 -0
- package/dist/hoi4_agent_tools/gui/layout.js +30 -18
- package/dist/hoi4_agent_tools/gui/renderer.js +23 -15
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +556 -0
- package/dist/hoi4_agent_tools/gui/scenario.js +44 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js +84 -9
- package/dist/hoi4_agent_tools/gui/studio.js +94 -16
- package/dist/hoi4_agent_tools/gui/validators.js +6 -1
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +23 -6
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/gui.md +22 -3
- 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 +1 -1
- 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
|
@@ -117,7 +117,27 @@ export function guiElementAttributeFidelity(key) {
|
|
|
117
117
|
return 'unsupported';
|
|
118
118
|
}
|
|
119
119
|
function normalizeAssetPath(value) {
|
|
120
|
-
return value
|
|
120
|
+
return value
|
|
121
|
+
.replaceAll('\\', '/')
|
|
122
|
+
.replace(/^\/+|^\.\//u, '')
|
|
123
|
+
.replace(/\/+/gu, '/')
|
|
124
|
+
.toLowerCase();
|
|
125
|
+
}
|
|
126
|
+
function textureNodeFor(textureNodes, texturePath) {
|
|
127
|
+
const normalized = normalizeAssetPath(texturePath);
|
|
128
|
+
const extension = path.posix.extname(normalized);
|
|
129
|
+
const rasterExtensions = ['.png', '.bmp', '.tga', '.dds'];
|
|
130
|
+
const candidates = rasterExtensions.includes(extension)
|
|
131
|
+
? [
|
|
132
|
+
normalized,
|
|
133
|
+
...rasterExtensions
|
|
134
|
+
.filter((candidate) => candidate !== extension)
|
|
135
|
+
.map((candidate) => `${normalized.slice(0, -extension.length)}${candidate}`),
|
|
136
|
+
]
|
|
137
|
+
: [normalized];
|
|
138
|
+
return candidates
|
|
139
|
+
.map((candidate) => textureNodes.get(candidate))
|
|
140
|
+
.find((value) => value !== undefined);
|
|
121
141
|
}
|
|
122
142
|
function scalarValue(value, constants) {
|
|
123
143
|
const expanded = constants.get(value) ?? value;
|
|
@@ -197,6 +217,26 @@ function paradoxColour(value) {
|
|
|
197
217
|
const argb = match[1];
|
|
198
218
|
return `#${argb.slice(2)}${argb.slice(0, 2)}`.toLowerCase();
|
|
199
219
|
}
|
|
220
|
+
function textColoursFrom(block) {
|
|
221
|
+
const textColours = childBlocks(block, 'textcolors')[0];
|
|
222
|
+
if (textColours === undefined)
|
|
223
|
+
return {};
|
|
224
|
+
const result = {};
|
|
225
|
+
for (const entry of assignments(textColours)) {
|
|
226
|
+
if (entry.value.type !== 'block')
|
|
227
|
+
continue;
|
|
228
|
+
const channels = entry.value.entries
|
|
229
|
+
.filter((channel) => channel.type === 'scalar')
|
|
230
|
+
.map((channel) => Number(channel.value));
|
|
231
|
+
if (channels.length < 3 || channels.slice(0, 3).some((channel) => !Number.isFinite(channel)))
|
|
232
|
+
continue;
|
|
233
|
+
const [red, green, blue] = channels.map((channel) => Math.max(0, Math.min(255, Math.round(channel))));
|
|
234
|
+
result[entry.key.value] = `#${red.toString(16).padStart(2, '0')}${green
|
|
235
|
+
.toString(16)
|
|
236
|
+
.padStart(2, '0')}${blue.toString(16).padStart(2, '0')}`;
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
200
240
|
function sizeFrom(block) {
|
|
201
241
|
const size = childBlocks(block, 'size')[0];
|
|
202
242
|
if (size === undefined)
|
|
@@ -343,7 +383,7 @@ function indexGuiElements(document, file, fileNodeId, nodes, edges, elements) {
|
|
|
343
383
|
}
|
|
344
384
|
}
|
|
345
385
|
}
|
|
346
|
-
function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites, fonts) {
|
|
386
|
+
function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites, fonts, globalTextColours) {
|
|
347
387
|
const walk = (block) => {
|
|
348
388
|
for (const assignment of assignments(block)) {
|
|
349
389
|
if (assignment.value.type !== 'block')
|
|
@@ -436,18 +476,27 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
436
476
|
.map((entry) => entry.value));
|
|
437
477
|
const colour = paradoxColour(firstScalarInsensitive(child, 'color', 'colour'));
|
|
438
478
|
const borderColour = paradoxColour(firstScalarInsensitive(child, 'border_color', 'borderColor', 'border_colour'));
|
|
439
|
-
const
|
|
479
|
+
const textColours = textColoursFrom(child);
|
|
480
|
+
const override = assignment.key.value === 'bitmapfont_override';
|
|
481
|
+
const id = deterministicId('gui_font', {
|
|
482
|
+
path: file.displayPath,
|
|
483
|
+
name,
|
|
484
|
+
override,
|
|
485
|
+
languages,
|
|
486
|
+
assetPaths,
|
|
487
|
+
});
|
|
440
488
|
const font = {
|
|
441
489
|
id,
|
|
442
490
|
name,
|
|
443
491
|
sourcePath: file.displayPath,
|
|
444
492
|
location: nodeLocation(document, assignment, name),
|
|
445
493
|
kind: 'bitmapfont',
|
|
446
|
-
override
|
|
494
|
+
override,
|
|
447
495
|
languages,
|
|
448
496
|
assetPaths,
|
|
449
497
|
...(colour === undefined ? {} : { colour }),
|
|
450
498
|
...(borderColour === undefined ? {} : { borderColour }),
|
|
499
|
+
...(Object.keys(textColours).length === 0 ? {} : { textColours }),
|
|
451
500
|
rawSource: raw(document, assignment),
|
|
452
501
|
};
|
|
453
502
|
addDomainEntry(fonts, font, 'font');
|
|
@@ -464,11 +513,15 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
464
513
|
assetPaths,
|
|
465
514
|
...(colour === undefined ? {} : { colour }),
|
|
466
515
|
...(borderColour === undefined ? {} : { borderColour }),
|
|
516
|
+
...(Object.keys(textColours).length === 0 ? {} : { textColours }),
|
|
467
517
|
},
|
|
468
518
|
});
|
|
469
519
|
addEdge(edges, 'contains', fileNodeId, id, true, {}, font.location);
|
|
470
520
|
}
|
|
471
521
|
}
|
|
522
|
+
if (assignment.key.value === 'bitmapfonts') {
|
|
523
|
+
Object.assign(globalTextColours, textColoursFrom(child));
|
|
524
|
+
}
|
|
472
525
|
walk(child);
|
|
473
526
|
}
|
|
474
527
|
};
|
|
@@ -750,8 +803,21 @@ function indexScriptedLocalisation(document, file, fileNodeId, nodes, edges, def
|
|
|
750
803
|
const name = firstScalar(assignment.value, 'name')?.value;
|
|
751
804
|
if (name === undefined)
|
|
752
805
|
continue;
|
|
753
|
-
const
|
|
754
|
-
|
|
806
|
+
const choices = childBlocks(assignment.value, 'text').flatMap((textBlock) => {
|
|
807
|
+
const localisationKey = firstScalarInsensitive(textBlock, 'localisation_key', 'localization_key');
|
|
808
|
+
if (localisationKey === undefined)
|
|
809
|
+
return [];
|
|
810
|
+
const trigger = childBlocks(textBlock, 'trigger')[0];
|
|
811
|
+
return [
|
|
812
|
+
{
|
|
813
|
+
localisationKey,
|
|
814
|
+
...(trigger === undefined ? {} : { triggerExpression: raw(document, trigger) }),
|
|
815
|
+
rawSource: raw(document, textBlock),
|
|
816
|
+
},
|
|
817
|
+
];
|
|
818
|
+
});
|
|
819
|
+
const localisationKeys = choices
|
|
820
|
+
.map(({ localisationKey }) => localisationKey)
|
|
755
821
|
.sort((left, right) => compareCodeUnits(left, right));
|
|
756
822
|
const id = deterministicId('scripted_localisation', { path: file.displayPath, name });
|
|
757
823
|
const location = nodeLocation(document, assignment, name);
|
|
@@ -761,6 +827,7 @@ function indexScriptedLocalisation(document, file, fileNodeId, nodes, edges, def
|
|
|
761
827
|
sourcePath: file.displayPath,
|
|
762
828
|
location,
|
|
763
829
|
localisationKeys,
|
|
830
|
+
choices,
|
|
764
831
|
rawSource: raw(document, assignment),
|
|
765
832
|
}, 'scripted localisation');
|
|
766
833
|
addNode(nodes, {
|
|
@@ -769,7 +836,13 @@ function indexScriptedLocalisation(document, file, fileNodeId, nodes, edges, def
|
|
|
769
836
|
name,
|
|
770
837
|
path: file.displayPath,
|
|
771
838
|
location,
|
|
772
|
-
metadata: {
|
|
839
|
+
metadata: {
|
|
840
|
+
localisationKeys,
|
|
841
|
+
choices: choices.map(({ localisationKey, triggerExpression }) => ({
|
|
842
|
+
localisationKey,
|
|
843
|
+
...(triggerExpression === undefined ? {} : { triggerExpression }),
|
|
844
|
+
})),
|
|
845
|
+
},
|
|
773
846
|
});
|
|
774
847
|
addEdge(edges, 'contains', fileNodeId, id, true, {}, location);
|
|
775
848
|
}
|
|
@@ -841,7 +914,7 @@ function linkGraph(nodes, edges, elements, sprites, fonts, scriptedGuis, scripte
|
|
|
841
914
|
for (const [index, texturePath] of [sprite.texturePath, sprite.texturePath2].entries()) {
|
|
842
915
|
if (texturePath === undefined)
|
|
843
916
|
continue;
|
|
844
|
-
const textureId = textureNodes
|
|
917
|
+
const textureId = textureNodeFor(textureNodes, texturePath);
|
|
845
918
|
addEdge(edges, 'uses_texture', sprite.id, textureId ?? `texture:${normalizeAssetPath(texturePath)}`, textureId !== undefined, { texturePath, slot: index + 1 }, sprite.location);
|
|
846
919
|
}
|
|
847
920
|
const candidates = [
|
|
@@ -1046,6 +1119,7 @@ export function buildGuiSourceGraph(files, sharedIndex) {
|
|
|
1046
1119
|
const elements = [];
|
|
1047
1120
|
const sprites = [];
|
|
1048
1121
|
const fonts = [];
|
|
1122
|
+
const textColours = {};
|
|
1049
1123
|
const scriptedGuis = [];
|
|
1050
1124
|
const animationSources = [];
|
|
1051
1125
|
const scriptedLocalisation = [];
|
|
@@ -1167,7 +1241,7 @@ export function buildGuiSourceGraph(files, sharedIndex) {
|
|
|
1167
1241
|
if (kind === 'gui_file')
|
|
1168
1242
|
indexGuiElements(document, file, fileNodeId, nodes, edges, elements);
|
|
1169
1243
|
if (kind === 'gfx_file') {
|
|
1170
|
-
indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites, fonts);
|
|
1244
|
+
indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites, fonts, textColours);
|
|
1171
1245
|
}
|
|
1172
1246
|
if (kind === 'scripted_gui_file') {
|
|
1173
1247
|
indexScriptedGuis(document, file, fileNodeId, nodes, edges, scriptedGuis);
|
|
@@ -1247,6 +1321,7 @@ export function buildGuiSourceGraph(files, sharedIndex) {
|
|
|
1247
1321
|
elements,
|
|
1248
1322
|
sprites,
|
|
1249
1323
|
fonts,
|
|
1324
|
+
textColours: Object.fromEntries(Object.entries(textColours).sort(([left], [right]) => compareCodeUnits(left, right))),
|
|
1250
1325
|
scriptedGuis,
|
|
1251
1326
|
animationSources,
|
|
1252
1327
|
scriptedLocalisation,
|
|
@@ -19,7 +19,8 @@ import { projectGuiGraphForArtifact } from './inspection-artifact.js';
|
|
|
19
19
|
import { buildGuiScene } from './layout.js';
|
|
20
20
|
import { GUI_VALIDATION_MAX_DIAGNOSTICS } from './limits.js';
|
|
21
21
|
import { compareGuiImages, galleryDimensions, renderGallerySvg, renderGuiScene, } from './renderer.js';
|
|
22
|
-
import {
|
|
22
|
+
import { generateGuiPreviewScenarios } from './scenario-generator.js';
|
|
23
|
+
import { parseGeneratedScenarioOptions, parsePreviewScenario } from './scenario.js';
|
|
23
24
|
import { buildGuiSourceGraph } from './source-graph.js';
|
|
24
25
|
import { assertGuiSourcePatchesSafe } from './source-patch.js';
|
|
25
26
|
import { validateGuiScene, validateResolutionDrift, validateStateMatrix } from './validators.js';
|
|
@@ -327,7 +328,7 @@ function localisationIconSpriteCandidates(token) {
|
|
|
327
328
|
const normalized = token.startsWith('GFX_') ? token : `GFX_${token}`;
|
|
328
329
|
return [...new Set([normalized, `${normalized}_texticon`, `${normalized}_text_icon`])];
|
|
329
330
|
}
|
|
330
|
-
function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNames = []) {
|
|
331
|
+
export function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNames = [], languages = ['l_english']) {
|
|
331
332
|
const spriteNames = new Set(additionalSpriteNames);
|
|
332
333
|
const fontNames = new Set();
|
|
333
334
|
const textValues = new Set();
|
|
@@ -346,6 +347,37 @@ function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNam
|
|
|
346
347
|
spriteNames.add(candidate);
|
|
347
348
|
}
|
|
348
349
|
}
|
|
350
|
+
const relatedScriptedGuiNames = new Set(graph.scriptedGuis
|
|
351
|
+
.filter((definition) => definition.windowName === windowName || definition.parentWindowName === windowName)
|
|
352
|
+
.map(({ name }) => name));
|
|
353
|
+
let addedRelatedScriptedGui = true;
|
|
354
|
+
while (addedRelatedScriptedGui) {
|
|
355
|
+
addedRelatedScriptedGui = false;
|
|
356
|
+
for (const definition of graph.scriptedGuis) {
|
|
357
|
+
if (definition.parentScriptedGui === undefined ||
|
|
358
|
+
!relatedScriptedGuiNames.has(definition.parentScriptedGui) ||
|
|
359
|
+
relatedScriptedGuiNames.has(definition.name))
|
|
360
|
+
continue;
|
|
361
|
+
relatedScriptedGuiNames.add(definition.name);
|
|
362
|
+
addedRelatedScriptedGui = true;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
const dynamicImageTokens = new Set(graph.scriptedGuis
|
|
366
|
+
.filter(({ name }) => relatedScriptedGuiNames.has(name))
|
|
367
|
+
.flatMap(({ propertyDefinitions }) => propertyDefinitions)
|
|
368
|
+
.flatMap(({ attributes }) => {
|
|
369
|
+
const image = Object.entries(attributes).find(([key]) => key.toLocaleLowerCase('en-US') === 'image')?.[1];
|
|
370
|
+
if (typeof image !== 'string')
|
|
371
|
+
return [];
|
|
372
|
+
const match = /^\[([^\]]+)\]$/u.exec(image);
|
|
373
|
+
return match?.[1] === undefined ? [] : [match[1]];
|
|
374
|
+
}));
|
|
375
|
+
for (const definition of graph.scriptedLocalisation) {
|
|
376
|
+
if (!dynamicImageTokens.has(definition.name))
|
|
377
|
+
continue;
|
|
378
|
+
for (const candidate of definition.localisationKeys)
|
|
379
|
+
spriteNames.add(candidate);
|
|
380
|
+
}
|
|
349
381
|
const spritesByName = new Map(graph.sprites.map((sprite) => [sprite.name.toLocaleLowerCase('en-US'), sprite]));
|
|
350
382
|
const selectedSprites = new Map();
|
|
351
383
|
const pendingSprites = [...spriteNames];
|
|
@@ -365,7 +397,17 @@ function referencedAssetPatternsForWindow(graph, windowName, additionalSpriteNam
|
|
|
365
397
|
}
|
|
366
398
|
const selectedManifests = graph.animationSources.filter(({ sprite }) => selectedSprites.has(sprite.toLocaleLowerCase('en-US')));
|
|
367
399
|
const selectedFontNames = new Set([...fontNames].map((name) => name.toLocaleLowerCase('en-US')));
|
|
368
|
-
const
|
|
400
|
+
const normalizedLanguages = new Set(languages.flatMap((language) => {
|
|
401
|
+
const normalized = language.toLocaleLowerCase('en-US');
|
|
402
|
+
return [normalized, normalized.replace(/^l_/u, '')];
|
|
403
|
+
}));
|
|
404
|
+
const selectedFonts = graph.fonts.filter(({ name, languages: fontLanguages }) => selectedFontNames.has(name.toLocaleLowerCase('en-US')) &&
|
|
405
|
+
(fontLanguages.length === 0 ||
|
|
406
|
+
fontLanguages.some((language) => {
|
|
407
|
+
const normalized = language.toLocaleLowerCase('en-US');
|
|
408
|
+
return (normalizedLanguages.has(normalized) ||
|
|
409
|
+
normalizedLanguages.has(normalized.replace(/^l_/u, '')));
|
|
410
|
+
})));
|
|
369
411
|
const references = [
|
|
370
412
|
...[...selectedSprites.values()].flatMap(({ texturePath, texturePath2 }) => [texturePath, texturePath2].filter((value) => value !== undefined)),
|
|
371
413
|
...selectedFonts.flatMap(({ assetPaths }) => assetPaths),
|
|
@@ -664,7 +706,7 @@ export class ScriptedGuiStudio {
|
|
|
664
706
|
const definitionFiles = mergeScannedFiles(layoutFiles, linkedDefinitions.files, localisation.files);
|
|
665
707
|
const definitions = this.graphForFiles(definitionFiles, `definitions:${workspaceId}`);
|
|
666
708
|
assertTargetWindowAvailable(definitions.graph, windowName);
|
|
667
|
-
const referencedPatterns = referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames);
|
|
709
|
+
const referencedPatterns = referencedAssetPatternsForWindow(definitions.graph, windowName, additionalSpriteNames, languages);
|
|
668
710
|
const exactReferenced = referencedPatterns.length === 0
|
|
669
711
|
? []
|
|
670
712
|
: await this.scanner.scan(workspace, {
|
|
@@ -702,10 +744,24 @@ export class ScriptedGuiStudio {
|
|
|
702
744
|
}
|
|
703
745
|
async lint(input) {
|
|
704
746
|
input.signal?.throwIfAborted();
|
|
705
|
-
const
|
|
706
|
-
const
|
|
747
|
+
const placeholderScenario = parsePreviewScenario(input.scenario);
|
|
748
|
+
const explicitRelatedScenarios = (input.relatedScenarios ?? []).map(parsePreviewScenario);
|
|
749
|
+
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [placeholderScenario.language, ...explicitRelatedScenarios.map(({ language }) => language)], scenarioSpriteNames([placeholderScenario, ...explicitRelatedScenarios]));
|
|
750
|
+
const generatedOptions = input.generatedScenarios === undefined
|
|
751
|
+
? undefined
|
|
752
|
+
: parseGeneratedScenarioOptions(input.generatedScenarios);
|
|
753
|
+
const generatedScenarios = generatedOptions === undefined
|
|
754
|
+
? []
|
|
755
|
+
: generateGuiPreviewScenarios(scanned.graph, input.windowName, placeholderScenario, generatedOptions);
|
|
756
|
+
const scenario = generatedScenarios[0] ?? placeholderScenario;
|
|
757
|
+
const relatedScenarios = [
|
|
758
|
+
...(generatedOptions?.preservePlaceholder === true && generatedScenarios.length > 0
|
|
759
|
+
? [placeholderScenario]
|
|
760
|
+
: []),
|
|
761
|
+
...generatedScenarios.slice(1),
|
|
762
|
+
...explicitRelatedScenarios,
|
|
763
|
+
];
|
|
707
764
|
assertUniqueScenarioIds([scenario, ...relatedScenarios]);
|
|
708
|
-
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [scenario.language, ...relatedScenarios.map(({ language }) => language)], scenarioSpriteNames([scenario, ...relatedScenarios]));
|
|
709
765
|
const budget = new RenderBudget();
|
|
710
766
|
const catalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, scenario.language);
|
|
711
767
|
const scene = await buildGuiScene(scanned.graph, scanned.files, input.windowName, scenario, catalog);
|
|
@@ -756,15 +812,37 @@ export class ScriptedGuiStudio {
|
|
|
756
812
|
input.signal?.throwIfAborted();
|
|
757
813
|
const budget = new RenderBudget();
|
|
758
814
|
const workspace = this.resolver.get(input.workspaceId, input.principal);
|
|
759
|
-
const
|
|
760
|
-
const
|
|
761
|
-
|
|
762
|
-
|
|
815
|
+
const placeholderScenario = parsePreviewScenario(input.scenario);
|
|
816
|
+
const explicitRelatedScenarios = (input.relatedScenarios ?? []).map(parsePreviewScenario);
|
|
817
|
+
const requestedBaselineScenario = input.comparisonScenario === undefined
|
|
818
|
+
? undefined
|
|
819
|
+
: parsePreviewScenario(input.comparisonScenario);
|
|
763
820
|
const scanned = await this.scanWindow(input.workspaceId, input.windowName, input.principal, input.signal, [
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
...
|
|
767
|
-
], scenarioSpriteNames([
|
|
821
|
+
placeholderScenario.language,
|
|
822
|
+
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario.language]),
|
|
823
|
+
...explicitRelatedScenarios.map(({ language }) => language),
|
|
824
|
+
], scenarioSpriteNames([
|
|
825
|
+
placeholderScenario,
|
|
826
|
+
...(requestedBaselineScenario === undefined ? [] : [requestedBaselineScenario]),
|
|
827
|
+
...explicitRelatedScenarios,
|
|
828
|
+
]));
|
|
829
|
+
const generatedOptions = input.generatedScenarios === undefined
|
|
830
|
+
? undefined
|
|
831
|
+
: parseGeneratedScenarioOptions(input.generatedScenarios);
|
|
832
|
+
const generatedScenarios = generatedOptions === undefined
|
|
833
|
+
? []
|
|
834
|
+
: generateGuiPreviewScenarios(scanned.graph, input.windowName, placeholderScenario, generatedOptions);
|
|
835
|
+
const scenario = generatedScenarios[0] ?? placeholderScenario;
|
|
836
|
+
const relatedScenarios = [
|
|
837
|
+
...(generatedOptions?.preservePlaceholder === true && generatedScenarios.length > 0
|
|
838
|
+
? [placeholderScenario]
|
|
839
|
+
: []),
|
|
840
|
+
...generatedScenarios.slice(1),
|
|
841
|
+
...explicitRelatedScenarios,
|
|
842
|
+
];
|
|
843
|
+
assertUniqueScenarioIds([scenario, ...relatedScenarios]);
|
|
844
|
+
const baselineScenario = requestedBaselineScenario ??
|
|
845
|
+
parsePreviewScenario({ ...scenario, id: `${scenario.id}-comparison`, state: 'normal' });
|
|
768
846
|
const catalog = new GuiAssetCatalog(scanned.graph, scanned.files, budget, scenario.language);
|
|
769
847
|
const scene = await buildGuiScene(scanned.graph, scanned.files, input.windowName, scenario, catalog);
|
|
770
848
|
const render = await renderGuiScene(scene, undefined, input.signal, budget);
|
|
@@ -1063,7 +1141,7 @@ export class ScriptedGuiStudio {
|
|
|
1063
1141
|
const scenario = parsePreviewScenario(input.scenario);
|
|
1064
1142
|
const renderBudget = new RenderBudget();
|
|
1065
1143
|
let proposedGraph = buildGuiSourceGraph(proposedFiles, this.engine.indexFiles(proposedFiles));
|
|
1066
|
-
const proposedAssetPatterns = referencedAssetPatternsForWindow(proposedGraph, input.windowName);
|
|
1144
|
+
const proposedAssetPatterns = referencedAssetPatternsForWindow(proposedGraph, input.windowName, [], [scenario.language]);
|
|
1067
1145
|
const exactProposedAssets = proposedAssetPatterns.length === 0
|
|
1068
1146
|
? []
|
|
1069
1147
|
: await this.scanner.scan(workspace, {
|
|
@@ -509,7 +509,12 @@ function validateReferencesAndScript(graph, scene, diagnostics) {
|
|
|
509
509
|
const selectedSpriteNames = new Set(scene.elements.flatMap(({ sprite }) => (sprite === undefined ? [] : [sprite.spriteName])));
|
|
510
510
|
const selectedSpriteIds = new Set(graph.sprites.filter(({ name }) => selectedSpriteNames.has(name)).map(({ id }) => id));
|
|
511
511
|
const selectedFontNames = new Set(scene.elements.flatMap(({ text }) => (text?.fontName === undefined ? [] : [text.fontName])));
|
|
512
|
-
const
|
|
512
|
+
const sceneLanguage = scene.scenario.language.toLocaleLowerCase('en-US');
|
|
513
|
+
const selectedFontIds = new Set(graph.fonts
|
|
514
|
+
.filter(({ name, languages }) => selectedFontNames.has(name) &&
|
|
515
|
+
(languages.length === 0 ||
|
|
516
|
+
languages.some((language) => language.toLocaleLowerCase('en-US') === sceneLanguage)))
|
|
517
|
+
.map(({ id }) => id));
|
|
513
518
|
const selectedManifestIds = new Set(graph.animationSources
|
|
514
519
|
.filter(({ sprite }) => selectedSpriteNames.has(sprite))
|
|
515
520
|
.map(({ id }) => id));
|
|
@@ -4,7 +4,7 @@ import { canonicalJson, compareCodeUnits, hashCanonical } from '../../core/canon
|
|
|
4
4
|
import { renderDimensionViolation, RenderBudget, RENDER_MAX_DIMENSION, } from '../../core/render-budget.js';
|
|
5
5
|
import { emptyServiceResult, ServiceError } from '../../core/result.js';
|
|
6
6
|
import { SOURCE_MAX_BYTES } from '../../core/source/index.js';
|
|
7
|
-
import { GuiHelperDocumentSchema, GuiPreviewScenarioSchema, GUI_TEXT_PACKAGE_MAX_FILES, ScriptedGuiStudio, compileGuiHelpers, } from '../../gui/index.js';
|
|
7
|
+
import { GuiHelperDocumentSchema, GuiGeneratedScenarioOptionsSchema, GuiPreviewScenarioSchema, GUI_TEXT_PACKAGE_MAX_FILES, ScriptedGuiStudio, compileGuiHelpers, } from '../../gui/index.js';
|
|
8
8
|
import { encodeGuiInspectionArtifact, projectGuiGraphForArtifact, } from '../../gui/inspection-artifact.js';
|
|
9
9
|
import { workspaceIdSchema, workspaceRelativePathSchema } from '../../schemas/common.js';
|
|
10
10
|
import { PACKAGE_VERSION } from '../../version.js';
|
|
@@ -92,6 +92,7 @@ const previewStateSchema = z.enum([
|
|
|
92
92
|
'missing-localisation',
|
|
93
93
|
]);
|
|
94
94
|
const compactGuiScenarioSchema = compactValidatedInputSchema(GuiPreviewScenarioSchema, `Complete GUI preview scenario: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
|
|
95
|
+
const compactGeneratedScenarioOptionsSchema = compactValidatedInputSchema(GuiGeneratedScenarioOptionsSchema, `Options: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
|
|
95
96
|
const compactGuiHelperSchema = compactValidatedInputSchema(GuiHelperDocumentSchema, `Complete GUI helper document: https://github.com/klimPaskov/hoi4-agent-tools/blob/v${PACKAGE_VERSION}/docs/gui.md`);
|
|
96
97
|
const guiBaseInput = z
|
|
97
98
|
.object({
|
|
@@ -106,6 +107,7 @@ const guiInspectInput = z
|
|
|
106
107
|
windowName: z.string().min(1).max(256).optional(),
|
|
107
108
|
scenario: compactGuiScenarioSchema.optional(),
|
|
108
109
|
relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
|
|
110
|
+
generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
|
|
109
111
|
})
|
|
110
112
|
.strict()
|
|
111
113
|
.superRefine((value, context) => {
|
|
@@ -122,6 +124,13 @@ const guiInspectInput = z
|
|
|
122
124
|
message: 'relatedScenarios requires a window scenario',
|
|
123
125
|
});
|
|
124
126
|
}
|
|
127
|
+
if (value.generatedScenarios !== undefined && value.scenario === undefined) {
|
|
128
|
+
context.addIssue({
|
|
129
|
+
code: 'custom',
|
|
130
|
+
path: ['generatedScenarios'],
|
|
131
|
+
message: 'generatedScenarios requires a window scenario',
|
|
132
|
+
});
|
|
133
|
+
}
|
|
125
134
|
});
|
|
126
135
|
const resolutionSchema = z
|
|
127
136
|
.object({
|
|
@@ -147,11 +156,11 @@ export function registerGuiTools(server, engine, context) {
|
|
|
147
156
|
const studio = new ScriptedGuiStudio(engine);
|
|
148
157
|
server.registerTool('hoi4.gui_inspect', {
|
|
149
158
|
title: 'Inspect scripted GUI',
|
|
150
|
-
description: 'Inspect GUI
|
|
159
|
+
description: 'Inspect GUI sources and scenarios.',
|
|
151
160
|
inputSchema: guiInspectInput,
|
|
152
161
|
outputSchema: guiScanOutputSchema,
|
|
153
162
|
annotations: artifactProducing,
|
|
154
|
-
}, async ({ workspaceId: requestedWorkspaceId, windowName, scenario, relatedScenarios }, extra) => {
|
|
163
|
+
}, async ({ workspaceId: requestedWorkspaceId, windowName, scenario, relatedScenarios, generatedScenarios, }, extra) => {
|
|
155
164
|
const workspaceId = await resolveServerWorkspaceId(engine, context, requestedWorkspaceId, extra.signal);
|
|
156
165
|
try {
|
|
157
166
|
const progress = progressReporter(extra);
|
|
@@ -166,6 +175,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
166
175
|
windowName,
|
|
167
176
|
scenario,
|
|
168
177
|
...(relatedScenarios === undefined ? {} : { relatedScenarios }),
|
|
178
|
+
generatedScenarios: generatedScenarios ?? {},
|
|
169
179
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
170
180
|
signal: progress.signal,
|
|
171
181
|
});
|
|
@@ -285,6 +295,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
285
295
|
...(input.relatedScenarios === undefined
|
|
286
296
|
? {}
|
|
287
297
|
: { relatedScenarios: input.relatedScenarios }),
|
|
298
|
+
generatedScenarios: input.generatedScenarios ?? {},
|
|
288
299
|
...(input.comparisonScenario === undefined
|
|
289
300
|
? {}
|
|
290
301
|
: { comparisonScenario: input.comparisonScenario }),
|
|
@@ -333,10 +344,11 @@ export function registerGuiTools(server, engine, context) {
|
|
|
333
344
|
states: z.array(previewStateSchema).max(14).optional(),
|
|
334
345
|
resolutions: z.array(resolutionSchema).min(1).max(16).optional(),
|
|
335
346
|
relatedScenarios: z.array(compactGuiScenarioSchema).max(32).optional(),
|
|
347
|
+
generatedScenarios: compactGeneratedScenarioOptionsSchema.optional(),
|
|
336
348
|
comparisonScenario: compactGuiScenarioSchema.optional(),
|
|
337
349
|
})
|
|
338
350
|
.strict()
|
|
339
|
-
.superRefine(({ scenario, states, resolutions, relatedScenarios }, context) => {
|
|
351
|
+
.superRefine(({ scenario, states, resolutions, relatedScenarios, generatedScenarios }, context) => {
|
|
340
352
|
const budget = new RenderBudget();
|
|
341
353
|
try {
|
|
342
354
|
const stateCount = states?.length ?? 14;
|
|
@@ -351,7 +363,12 @@ export function registerGuiTools(server, engine, context) {
|
|
|
351
363
|
]) {
|
|
352
364
|
budget.reserve(resolution.width, resolution.height, 'GUI resolution variant');
|
|
353
365
|
}
|
|
354
|
-
const
|
|
366
|
+
const generatedCount = generatedScenarios?.enabled === false ? 0 : (generatedScenarios?.count ?? 1);
|
|
367
|
+
const scenarioCount = generatedCount === 0
|
|
368
|
+
? 1 + (relatedScenarios?.length ?? 0)
|
|
369
|
+
: generatedCount +
|
|
370
|
+
(generatedScenarios?.preservePlaceholder === false ? 0 : 1) +
|
|
371
|
+
(relatedScenarios?.length ?? 0);
|
|
355
372
|
const stateColumns = Math.min(3, Math.max(1, stateCount));
|
|
356
373
|
const stateRows = Math.max(1, Math.ceil(stateCount / stateColumns));
|
|
357
374
|
budget.reserve(stateColumns * 420, 46 + stateRows * 280, 'GUI state gallery');
|
|
@@ -374,7 +391,7 @@ export function registerGuiTools(server, engine, context) {
|
|
|
374
391
|
});
|
|
375
392
|
server.registerTool('hoi4.gui_render', {
|
|
376
393
|
title: 'Render scripted GUI artifacts',
|
|
377
|
-
description: 'Render
|
|
394
|
+
description: 'Render GUI states, resolutions, generated or explicit scenarios, hierarchy, comparisons, and diagnostics.',
|
|
378
395
|
inputSchema: renderInput,
|
|
379
396
|
outputSchema: guiRenderOutputSchema,
|
|
380
397
|
annotations: artifactProducing,
|
|
@@ -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.8.
|
|
3
|
+
export const PACKAGE_VERSION = '2.8.6';
|
|
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/gui.md
CHANGED
|
@@ -33,12 +33,31 @@ Inspection checks missing assets and localisation, invalid sizes, overlap, clipp
|
|
|
33
33
|
|
|
34
34
|
The renderer resolves sprites and fonts through the complete mod, dependency, and installed-game load order. Mod windows therefore render ordinary vanilla sprites, tiled vanilla panels, progress bars, masked shields, button frames, and localisation icons such as `£command_power` without copying game assets into the mod. Case differences in GFX and font identifiers do not prevent a valid asset from resolving.
|
|
35
35
|
|
|
36
|
-
Bitmap text uses the language-appropriate HOI4 font definition and its real glyph atlas. Header, serif, compact, typewriter, black, inverted, and other declared faces keep their own glyph shapes, native metrics, face colour, border colour, and
|
|
36
|
+
Bitmap text uses the language-appropriate HOI4 font definition and its real glyph atlas. Header, serif, compact, typewriter, black, inverted, and other declared faces keep their own glyph shapes, native metrics, face colour, border colour, language override, and font-specific `textcolors` palette. HOI4 localisation colour runs such as `§Y` and `§R` multiply the selected palette colour into the native glyph pixels, preserving the atlas's sharp antialiasing and dark shadow pixels instead of flattening the glyph into a blurred monochrome mask or switching to the tool font. The default language uses the base font rather than an unrelated language override, and the renderer recognizes installed-game atlases whose filename omits a page suffix declared by the matching `.fnt` file.
|
|
37
37
|
|
|
38
38
|
Layout follows Clausewitz orientation and element-origin anchors, including `CENTER_LEFT`, `CENTER_RIGHT`, `CENTER_UP`, `CENTER_DOWN`, `origo`, `centerposition`, inherited container coordinate origins, local scale, and both `%` and `%%` relative dimensions. Text boxes use native font metrics together with `maxWidth`, `maxHeight`, alignment, wrapping, and `fixedsize` clipping. Cornered tile sprites use fixed nine-slice borders and optional center tiling; progress bars composite their filled and background textures; masked shields composite their background and mask textures.
|
|
39
39
|
|
|
40
40
|
Scripted-GUI composition follows `parent_scripted_gui` chains even when the linked child window is a top-level sibling in the `.gui` file. Literal `always = yes` and `always = no` visibility and click-enabled blocks are applied automatically. A scenario's `values` object supplies concrete runtime values in one place: variable names resolve numeric text, scripted-localisation names resolve dynamic labels, a progress-bar element name sets its fill value, and element keys ending in `.image`, `.frame`, `.x`, `.y`, `.visible`, or `.enabled` update that visual property. Scripted-GUI property expressions such as `image = "[GetMeterSprite]"` or `x = meter_fill_width` consume matching scenario values automatically. Dynamic lists instantiate their declared `entry_container` or `country_scope_entry_container`; each scenario row can select the country template with `countryScope`, choose an explicit `entryContainer`, and provide row-scoped text, image, frame, position, visibility, and enabled values.
|
|
41
41
|
|
|
42
|
+
`hoi4.gui_inspect` and `hoi4.gui_render` generate a plausible seeded runtime scenario by default. The generator follows the complete `parent_scripted_gui` surface, discovers numeric and text tokens in GUI localisation and scripted-GUI properties, chooses real scripted-localisation text and sprite candidates, applies simple source thresholds, fills meters within their declared ranges, populates dynamic lists with coherent rows, selects one overlapping tab pane, keeps active and idle controls mutually exclusive, and leaves values explicitly supplied by the caller unchanged. Normal controls and animation frames remain stable unless variation is explicitly requested. Every explicit `relatedScenarios` entry is rendered beside the generated view.
|
|
43
|
+
|
|
44
|
+
Generation is reproducible. Set `generatedScenarios.seed` to any stable string for an exact rerun, use `count` for several plausible versions, or configure numeric bounds, integer or decimal values, list row bounds, text samples, visibility probability, and state variation. The generated view is the default. Set `enabled` to `false` when only the supplied placeholder scenario should render, or set `preservePlaceholder` to `true` to include the placeholder beside generated scenarios.
|
|
45
|
+
|
|
46
|
+
```json
|
|
47
|
+
{
|
|
48
|
+
"generatedScenarios": {
|
|
49
|
+
"seed": "chaos-meter-review",
|
|
50
|
+
"count": 3,
|
|
51
|
+
"numericMinimum": 0,
|
|
52
|
+
"numericMaximum": 100,
|
|
53
|
+
"listRowsMinimum": 2,
|
|
54
|
+
"listRowsMaximum": 8,
|
|
55
|
+
"trueProbability": 0.75,
|
|
56
|
+
"textSamples": ["Stable", "Escalating", "Critical"]
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
42
61
|
Preview scenarios can include exact layout expectations:
|
|
43
62
|
|
|
44
63
|
```json
|
|
@@ -70,10 +89,10 @@ GUI rewrites keep complete validation evidence in a linked JSON resource and ret
|
|
|
70
89
|
|
|
71
90
|
Large inspections, renders, and rewrites send periodic MCP progress heartbeats while a service stage is still running. Clients that request progress can reset their idle request timer from these notifications; there is no 180-second GUI operation limit in the server, and cancellation still follows the request signal.
|
|
72
91
|
|
|
73
|
-
##
|
|
92
|
+
## Scenario fidelity
|
|
74
93
|
|
|
75
94
|
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.
|
|
76
95
|
|
|
77
|
-
|
|
96
|
+
The generated primary view replaces discovered numeric and text runtime tokens with plausible values. Set `generatedScenarios.preservePlaceholder` to `true`, or disable generation, when the same run should also show unresolved numeric values such as `[?variable|format]` as `[X]` and unresolved text-returning scripted or scoped localisation such as `[GetStatusText]`, `[FROM.GetName]`, and `[?leader_scope.GetName]` as `[dynamic_loc]`. Put `variable` or `GetStatusText` in the supplied scenario's `values` object when an exact value matters; explicit values always take precedence over generated ones and can drive the related meter or state-dependent controls. Supported HOI4 `§` localisation colour controls use the active font's palette and `§!` restores the face colour.
|
|
78
97
|
|
|
79
98
|
A rewrite stops if malformed or unsupported GUI script makes the requested change ambiguous. Frame-sheet animation, nine-slice tiling, progress composition, masking, and the primary and secondary textures referenced by vanilla GFX definitions render offline. Engine shader programs, hardcoded controls, and dynamic values that are not supplied by a scenario remain fidelity-report entries; an unexecuted shader never hides its resolved base texture.
|
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.8.
|
|
4
|
+
"version": "2.8.6",
|
|
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",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/schemas/gui-scenario.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.8.
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v2.8.6/schemas/map-operation.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"oneOf": [
|
|
5
5
|
{
|