hoi4-agent-tools 2.8.11 → 3.0.1
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 +16 -0
- package/README.md +38 -11
- package/dist/hoi4_agent_tools/core/configuration.js +16 -3
- package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +10 -1
- package/dist/hoi4_agent_tools/core/engine.js +3 -0
- package/dist/hoi4_agent_tools/core/scanner.js +63 -7
- package/dist/hoi4_agent_tools/event/service.js +30 -23
- package/dist/hoi4_agent_tools/gui/assets.js +218 -0
- package/dist/hoi4_agent_tools/gui/layout.js +70 -9
- package/dist/hoi4_agent_tools/gui/renderer.js +80 -11
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +36 -0
- package/dist/hoi4_agent_tools/gui/studio.js +90 -25
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +1 -1
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +9 -5
- package/dist/hoi4_agent_tools/probability/service.js +81 -14
- package/dist/hoi4_agent_tools/schemas/probability.js +2 -0
- 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/development.md +2 -0
- package/docs/gui.md +14 -3
- package/docs/images/readme/chaos-meter-ingame.png +0 -0
- package/docs/images/readme/chaos-meter-mcp.png +0 -0
- package/docs/images/readme/communist-insurgency-ingame.png +0 -0
- package/docs/images/readme/communist-insurgency-mcp.png +0 -0
- package/docs/images/readme/event-chain-mcp.png +0 -0
- package/docs/images/readme/focus-tree-mcp.png +0 -0
- package/docs/images/readme/map-mcp.png +0 -0
- package/docs/images/readme/probability-mcp.png +0 -0
- package/docs/images/readme/settings-advanced-mcp.png +0 -0
- package/docs/images/readme/settings-events-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 +3 -1
- package/schemas/configuration.schema.json +3 -3
- 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 +58 -39
- 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
|
@@ -46,7 +46,7 @@ function technologyNode(graph, technologyId, placement) {
|
|
|
46
46
|
kind: technology.kind,
|
|
47
47
|
sourcePath: technology.source.path,
|
|
48
48
|
sourceLine: technology.source.location.start.line,
|
|
49
|
-
layoutSize: technology.layoutSize,
|
|
49
|
+
layoutSize: placement?.layoutSize ?? technology.layoutSize,
|
|
50
50
|
iconSprite: technology.icon.sprite,
|
|
51
51
|
...(placement === undefined ? {} : { placement }),
|
|
52
52
|
};
|
|
@@ -61,6 +61,7 @@ function sortedUniqueNodes(values) {
|
|
|
61
61
|
function viewSelection(graph, options) {
|
|
62
62
|
const nodes = [];
|
|
63
63
|
const edges = [];
|
|
64
|
+
let yearMarkers = [];
|
|
64
65
|
let title;
|
|
65
66
|
let sourceAccurate = false;
|
|
66
67
|
if (options.view === 'summary') {
|
|
@@ -117,6 +118,7 @@ function viewSelection(graph, options) {
|
|
|
117
118
|
.filter(({ from, to }) => selected.has(from) && selected.has(to))
|
|
118
119
|
.map(({ id, from, to, kind }) => ({ id, from, to, kind })));
|
|
119
120
|
sourceAccurate = true;
|
|
121
|
+
yearMarkers = graph.yearMarkers.filter(({ folderId }) => folderId === options.folderId);
|
|
120
122
|
}
|
|
121
123
|
else if (options.view === 'dependencies' || options.view === 'technology') {
|
|
122
124
|
const ids = options.technologyId === undefined
|
|
@@ -407,6 +409,7 @@ function viewSelection(graph, options) {
|
|
|
407
409
|
return {
|
|
408
410
|
nodes: selectedNodes,
|
|
409
411
|
edges: selectedEdges,
|
|
412
|
+
yearMarkers,
|
|
410
413
|
sourceAccurate,
|
|
411
414
|
title,
|
|
412
415
|
payload: {
|
|
@@ -416,6 +419,7 @@ function viewSelection(graph, options) {
|
|
|
416
419
|
generatedAnalysisLayout: !sourceAccurate,
|
|
417
420
|
nodes: selectedNodes,
|
|
418
421
|
edges: selectedEdges,
|
|
422
|
+
yearMarkers,
|
|
419
423
|
graphRevision: graph.revision,
|
|
420
424
|
analysisBoundary: graph.analysisBoundary,
|
|
421
425
|
},
|
|
@@ -424,38 +428,61 @@ function viewSelection(graph, options) {
|
|
|
424
428
|
function issueKey(issue) {
|
|
425
429
|
return sha256Bytes(canonicalJson({ code: issue.code, details: issue.details })).slice(0, 20);
|
|
426
430
|
}
|
|
427
|
-
function layoutFolder(nodes) {
|
|
428
|
-
const dimensions = (node) => node.
|
|
429
|
-
? { width:
|
|
430
|
-
:
|
|
431
|
+
function layoutFolder(nodes, yearMarkers) {
|
|
432
|
+
const dimensions = (node) => node.placement?.layoutWidth !== undefined && node.placement.layoutHeight !== undefined
|
|
433
|
+
? { width: node.placement.layoutWidth, height: node.placement.layoutHeight }
|
|
434
|
+
: node.layoutSize === 'small'
|
|
435
|
+
? { width: SMALL_NODE_WIDTH, height: SMALL_NODE_HEIGHT }
|
|
436
|
+
: { width: LARGE_NODE_WIDTH, height: LARGE_NODE_HEIGHT };
|
|
431
437
|
const sourcePixel = nodes.filter(({ placement }) => placement?.pixelX !== undefined && placement.pixelY !== undefined);
|
|
432
438
|
if (sourcePixel.length === nodes.length && sourcePixel.length > 0) {
|
|
433
|
-
const
|
|
434
|
-
const
|
|
435
|
-
|
|
439
|
+
const sourceNodeX = (node) => node.placement.pixelX + (node.placement?.layoutOffsetX ?? 0);
|
|
440
|
+
const sourceNodeY = (node) => node.placement.pixelY + (node.placement?.layoutOffsetY ?? 0);
|
|
441
|
+
const minimumX = Math.min(...sourcePixel.map(sourceNodeX), ...yearMarkers.map(({ position }) => position.x));
|
|
442
|
+
const minimumY = Math.min(...sourcePixel.map(sourceNodeY), ...yearMarkers.map(({ position }) => position.y));
|
|
443
|
+
const positionedNodes = sourcePixel.map((node) => {
|
|
436
444
|
const size = dimensions(node);
|
|
437
445
|
return {
|
|
438
446
|
...node,
|
|
439
|
-
x: PADDING + node
|
|
440
|
-
y: HEADER_HEIGHT + PADDING + node
|
|
447
|
+
x: PADDING + sourceNodeX(node) - minimumX,
|
|
448
|
+
y: HEADER_HEIGHT + PADDING + sourceNodeY(node) - minimumY,
|
|
441
449
|
...size,
|
|
442
450
|
};
|
|
443
451
|
});
|
|
452
|
+
const positionedYearMarkers = yearMarkers.map((marker) => ({
|
|
453
|
+
...marker,
|
|
454
|
+
x: PADDING + marker.position.x - minimumX,
|
|
455
|
+
y: HEADER_HEIGHT + PADDING + marker.position.y - minimumY,
|
|
456
|
+
}));
|
|
457
|
+
const xValues = yearMarkers.map(({ position }) => position.x);
|
|
458
|
+
const yValues = yearMarkers.map(({ position }) => position.y);
|
|
459
|
+
const xSpan = xValues.length === 0 ? 0 : Math.max(...xValues) - Math.min(...xValues);
|
|
460
|
+
const ySpan = yValues.length === 0 ? 0 : Math.max(...yValues) - Math.min(...yValues);
|
|
461
|
+
return {
|
|
462
|
+
nodes: positionedNodes,
|
|
463
|
+
yearMarkers: positionedYearMarkers,
|
|
464
|
+
...(yearMarkers.length === 0
|
|
465
|
+
? {}
|
|
466
|
+
: { yearAxis: xSpan >= ySpan ? 'horizontal' : 'vertical' }),
|
|
467
|
+
};
|
|
444
468
|
}
|
|
445
469
|
const branchRoots = [
|
|
446
470
|
...new Set(nodes.map(({ placement }) => placement?.branchRootId ?? '<unresolved>')),
|
|
447
471
|
].sort(compareCodeUnits);
|
|
448
472
|
const lanes = new Map(branchRoots.map((id, index) => [id, index]));
|
|
449
|
-
return
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
473
|
+
return {
|
|
474
|
+
nodes: nodes.map((node, index) => {
|
|
475
|
+
const placement = node.placement;
|
|
476
|
+
const lane = lanes.get(placement?.branchRootId ?? '<unresolved>') ?? 0;
|
|
477
|
+
const size = dimensions(node);
|
|
478
|
+
const x = PADDING + lane * 680 + (placement?.x ?? index % 3) * (LARGE_NODE_WIDTH + GAP_X);
|
|
479
|
+
const y = HEADER_HEIGHT +
|
|
480
|
+
PADDING +
|
|
481
|
+
(placement?.y ?? Math.floor(index / 3)) * (LARGE_NODE_HEIGHT + GAP_Y);
|
|
482
|
+
return { ...node, x, y, ...size };
|
|
483
|
+
}),
|
|
484
|
+
yearMarkers: [],
|
|
485
|
+
};
|
|
459
486
|
}
|
|
460
487
|
function layoutGenerated(nodes, edges) {
|
|
461
488
|
const ids = new Set(nodes.map(({ id }) => id));
|
|
@@ -544,9 +571,9 @@ function colours(kind) {
|
|
|
544
571
|
removed: { fill: '#4a2828', stroke: '#ef7d7d' },
|
|
545
572
|
}[kind];
|
|
546
573
|
}
|
|
547
|
-
function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris) {
|
|
548
|
-
const maximumX = Math.max(PADDING + LARGE_NODE_WIDTH, ...positioned.map(({ x, width }) => x + width));
|
|
549
|
-
const maximumY = Math.max(HEADER_HEIGHT + LARGE_NODE_HEIGHT, ...positioned.map(({ y, height }) => y + height));
|
|
574
|
+
function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris, yearMarkers, yearAxis) {
|
|
575
|
+
const maximumX = Math.max(PADDING + LARGE_NODE_WIDTH, ...positioned.map(({ x, width }) => x + width), ...yearMarkers.map(({ x }) => x + 70));
|
|
576
|
+
const maximumY = Math.max(HEADER_HEIGHT + LARGE_NODE_HEIGHT, ...positioned.map(({ y, height }) => y + height), ...yearMarkers.map(({ y }) => y + 30));
|
|
550
577
|
const width = Math.ceil(maximumX + PADDING);
|
|
551
578
|
const height = Math.ceil(maximumY + PADDING);
|
|
552
579
|
if (width > 16_384 || height > 16_384 || width * height > RENDER_MAX_PIXELS)
|
|
@@ -556,6 +583,19 @@ function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris) {
|
|
|
556
583
|
});
|
|
557
584
|
const byId = new Map(positioned.map((node) => [node.id, node]));
|
|
558
585
|
const text = new DeterministicSvgTextRenderer();
|
|
586
|
+
const yearGuideSvg = yearMarkers.map((marker) => {
|
|
587
|
+
const guide = yearAxis === 'vertical'
|
|
588
|
+
? `<line x1="${PADDING}" y1="${marker.y + 15}" x2="${width - PADDING}" y2="${marker.y + 15}" stroke="#34495c" stroke-width="1" stroke-dasharray="5 7"/>`
|
|
589
|
+
: `<line x1="${marker.x + 24}" y1="${HEADER_HEIGHT}" x2="${marker.x + 24}" y2="${height - PADDING}" stroke="#34495c" stroke-width="1" stroke-dasharray="5 7"/>`;
|
|
590
|
+
const label = text.render(String(marker.year), {
|
|
591
|
+
x: marker.x,
|
|
592
|
+
y: marker.y + 22,
|
|
593
|
+
fontSize: 18,
|
|
594
|
+
fill: '#c7d4df',
|
|
595
|
+
weight: 700,
|
|
596
|
+
});
|
|
597
|
+
return `<g data-tech-year="${marker.year}" data-year-axis="${yearAxis ?? 'horizontal'}" data-source-path="${escapeXml(marker.sourcePath)}" data-source-line="${marker.location.start.line}">${guide}${label}</g>`;
|
|
598
|
+
});
|
|
559
599
|
const edgeSvg = edges.flatMap((edge) => {
|
|
560
600
|
const from = byId.get(edge.from);
|
|
561
601
|
const to = byId.get(edge.to);
|
|
@@ -624,7 +664,7 @@ function svgFor(titleText, positioned, edges, sourceAccurate, iconDataUris) {
|
|
|
624
664
|
return {
|
|
625
665
|
width,
|
|
626
666
|
height,
|
|
627
|
-
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(titleText)}"><defs><marker id="tech-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#8298ad"/></marker>${text.definitions()}</defs><rect width="100%" height="100%" fill="#0d1721"/>${title}${mode}<g>${edgeSvg.join('')}</g><g>${nodeSvg.join('')}</g></svg>`,
|
|
667
|
+
svg: `<svg xmlns="http://www.w3.org/2000/svg" width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" role="img" aria-label="${escapeXml(titleText)}"><defs><marker id="tech-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto-start-reverse"><path d="M 0 0 L 10 5 L 0 10 z" fill="#8298ad"/></marker>${text.definitions()}</defs><rect width="100%" height="100%" fill="#0d1721"/>${title}${mode}<g>${yearGuideSvg.join('')}</g><g>${edgeSvg.join('')}</g><g>${nodeSvg.join('')}</g></svg>`,
|
|
628
668
|
};
|
|
629
669
|
}
|
|
630
670
|
function htmlFor(title, svg, json) {
|
|
@@ -637,16 +677,17 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
637
677
|
const retained = selection.nodes.slice(0, maximum);
|
|
638
678
|
const retainedIds = new Set(retained.map(({ id }) => id));
|
|
639
679
|
const edges = selection.edges.filter(({ from, to }) => retainedIds.has(from) && retainedIds.has(to));
|
|
640
|
-
const
|
|
641
|
-
? layoutFolder(retained)
|
|
642
|
-
: layoutGenerated(retained, edges);
|
|
680
|
+
const folderLayout = selection.sourceAccurate
|
|
681
|
+
? layoutFolder(retained, selection.yearMarkers)
|
|
682
|
+
: { nodes: layoutGenerated(retained, edges), yearMarkers: [] };
|
|
683
|
+
const positioned = folderLayout.nodes;
|
|
643
684
|
const iconDataUris = options.iconDataUris ?? {};
|
|
644
685
|
const requestedIconSprites = [
|
|
645
686
|
...new Set(retained.flatMap(({ iconSprite }) => (iconSprite === undefined ? [] : [iconSprite]))),
|
|
646
687
|
].sort(compareCodeUnits);
|
|
647
688
|
const renderedIconSprites = requestedIconSprites.filter((sprite) => iconDataUris[sprite] !== undefined);
|
|
648
689
|
const unresolvedIconSprites = requestedIconSprites.filter((sprite) => iconDataUris[sprite] === undefined);
|
|
649
|
-
const rendered = svgFor(selection.title, positioned, edges, selection.sourceAccurate, iconDataUris);
|
|
690
|
+
const rendered = svgFor(selection.title, positioned, edges, selection.sourceAccurate, iconDataUris, folderLayout.yearMarkers, folderLayout.yearAxis);
|
|
650
691
|
const budget = options.budget ?? new RenderBudget();
|
|
651
692
|
budget.reserve(rendered.width, rendered.height, `technology ${options.view} render`);
|
|
652
693
|
budget.reserveRasterOperation(`technology-render:${graph.revision}:${options.view}:${sha256Bytes(rendered.svg)}`, `technology ${options.view} rasterization`);
|
|
@@ -667,6 +708,10 @@ export async function renderTechnologyGraph(graph, options) {
|
|
|
667
708
|
unresolvedSprites: unresolvedIconSprites,
|
|
668
709
|
},
|
|
669
710
|
render: { width: rendered.width, height: rendered.height },
|
|
711
|
+
yearGuide: {
|
|
712
|
+
axis: folderLayout.yearAxis ?? null,
|
|
713
|
+
markerCount: folderLayout.yearMarkers.length,
|
|
714
|
+
},
|
|
670
715
|
})}\n`;
|
|
671
716
|
const html = options.includeHtml === true ? htmlFor(selection.title, rendered.svg, json) : undefined;
|
|
672
717
|
return {
|
|
@@ -10,6 +10,7 @@ import { GuiAssetCatalog } from '../gui/assets.js';
|
|
|
10
10
|
import { buildGuiSourceGraph } from '../gui/source-graph.js';
|
|
11
11
|
import { compareTechnologyGraphs } from './compare.js';
|
|
12
12
|
import { buildTechnologyGraph, technologyAssetPatterns } from './graph.js';
|
|
13
|
+
import { TECHNOLOGY_GRAPH_SCHEMA_VERSION } from './model.js';
|
|
13
14
|
import { analyzeTechnologyImpact, discoverTechnologyFolders, explainTechnology, inspectTechnologyUnlocks, lintTechnologyGraph, technologyBonusCoverage, technologyScanReport, traceTechnology, } from './queries.js';
|
|
14
15
|
import { renderTechnologyGraph, technologyRenderIconSprites, } from './render.js';
|
|
15
16
|
const FRAGMENT_CACHE_ENTRIES = 8_192;
|
|
@@ -293,13 +294,15 @@ function validateGraph(value) {
|
|
|
293
294
|
throw new ServiceError('TECH_GRAPH_ARTIFACT_INVALID', 'Technology graph artifact is not an object');
|
|
294
295
|
const graph = value;
|
|
295
296
|
const sourceHashes = value.sourceHashes;
|
|
296
|
-
if (graph.schemaVersion !==
|
|
297
|
+
if (graph.schemaVersion !== TECHNOLOGY_GRAPH_SCHEMA_VERSION ||
|
|
297
298
|
typeof graph.workspaceId !== 'string' ||
|
|
298
299
|
typeof graph.workspaceIdentity !== 'string' ||
|
|
299
300
|
typeof graph.revision !== 'string' ||
|
|
300
301
|
!Array.isArray(graph.technologies) ||
|
|
301
302
|
!Array.isArray(graph.edges) ||
|
|
302
303
|
!Array.isArray(graph.placements) ||
|
|
304
|
+
!Array.isArray(graph.itemLayouts) ||
|
|
305
|
+
!Array.isArray(graph.yearMarkers) ||
|
|
303
306
|
sourceHashes === null ||
|
|
304
307
|
typeof sourceHashes !== 'object')
|
|
305
308
|
throw new ServiceError('TECH_GRAPH_ARTIFACT_INVALID', 'Technology graph artifact has an invalid schema');
|
|
@@ -350,11 +350,7 @@ function parseTechnologyDefinitions(file, document, fragment) {
|
|
|
350
350
|
].sort(compareCodeUnits);
|
|
351
351
|
const layoutExpression = firstValue(block, 'force_use_small_tech_layout');
|
|
352
352
|
const smallLayout = booleanValue(layoutExpression);
|
|
353
|
-
const layoutSize =
|
|
354
|
-
? 'large'
|
|
355
|
-
: smallLayout === true
|
|
356
|
-
? 'small'
|
|
357
|
-
: 'unknown';
|
|
353
|
+
const layoutSize = smallLayout === true ? 'small' : 'unknown';
|
|
358
354
|
const unsupportedFields = fragment.placements
|
|
359
355
|
.filter(({ technologyId, xExpression, yExpression }) => technologyId === id && (xExpression !== undefined || yExpression !== undefined))
|
|
360
356
|
.map((placement) => ({
|
|
@@ -581,12 +577,22 @@ function parseModernDoctrines(file, document, fragment) {
|
|
|
581
577
|
}
|
|
582
578
|
}
|
|
583
579
|
}
|
|
580
|
+
function technologyFolderContainer(namedContainers) {
|
|
581
|
+
return [...namedContainers].reverse().find((name) => /_folder(?:_|$)/iu.test(name));
|
|
582
|
+
}
|
|
583
|
+
function technologyItemLayoutName(name) {
|
|
584
|
+
const small = /^techtree_(.+)_small_item$/iu.exec(name);
|
|
585
|
+
if (small?.[1] !== undefined)
|
|
586
|
+
return { folderId: small[1], layoutSize: 'small' };
|
|
587
|
+
const large = /^techtree_(.+)_item$/iu.exec(name);
|
|
588
|
+
if (large?.[1] !== undefined)
|
|
589
|
+
return { folderId: large[1], layoutSize: 'large' };
|
|
590
|
+
return undefined;
|
|
591
|
+
}
|
|
584
592
|
function parseTechnologyGridboxes(file, document, fragment, signal) {
|
|
585
593
|
if (!normalizedPath(file).endsWith('.gui'))
|
|
586
594
|
return;
|
|
587
|
-
const stack = [
|
|
588
|
-
{ block: document.root, namedContainers: [] },
|
|
589
|
-
];
|
|
595
|
+
const stack = [{ block: document.root, namedContainers: [], offsetX: 0, offsetY: 0 }];
|
|
590
596
|
let visited = 0;
|
|
591
597
|
while (stack.length > 0) {
|
|
592
598
|
if (visited % 128 === 0)
|
|
@@ -599,11 +605,14 @@ function parseTechnologyGridboxes(file, document, fragment, signal) {
|
|
|
599
605
|
const block = assignment.value;
|
|
600
606
|
const elementType = assignment.key.value.toLowerCase();
|
|
601
607
|
const name = firstScalar(block, 'name');
|
|
608
|
+
const position = childBlocks(block, 'position')[0];
|
|
609
|
+
const localX = numericLiteral(position === undefined ? undefined : firstValue(position, 'x'));
|
|
610
|
+
const localY = numericLiteral(position === undefined ? undefined : firstValue(position, 'y'));
|
|
611
|
+
const absoluteX = current.offsetX + (localX ?? 0);
|
|
612
|
+
const absoluteY = current.offsetY + (localY ?? 0);
|
|
613
|
+
const folderId = technologyFolderContainer(current.namedContainers);
|
|
602
614
|
if (elementType === 'gridboxtype' && name?.value.endsWith('_tree') === true) {
|
|
603
|
-
const position = childBlocks(block, 'position')[0];
|
|
604
615
|
const slotSize = childBlocks(block, 'slotsize')[0];
|
|
605
|
-
const x = numericLiteral(position === undefined ? undefined : firstValue(position, 'x'));
|
|
606
|
-
const y = numericLiteral(position === undefined ? undefined : firstValue(position, 'y'));
|
|
607
616
|
const width = numericLiteral(slotSize === undefined ? undefined : firstValue(slotSize, 'width'));
|
|
608
617
|
const height = numericLiteral(slotSize === undefined ? undefined : firstValue(slotSize, 'height'));
|
|
609
618
|
const gridbox = {
|
|
@@ -613,16 +622,14 @@ function parseTechnologyGridboxes(file, document, fragment, signal) {
|
|
|
613
622
|
offset: assignment.start,
|
|
614
623
|
}),
|
|
615
624
|
name: name.value,
|
|
616
|
-
...(
|
|
617
|
-
? {}
|
|
618
|
-
: { folderId: current.namedContainers.at(-1) }),
|
|
625
|
+
...(folderId === undefined ? {} : { folderId }),
|
|
619
626
|
position: {
|
|
620
|
-
...(
|
|
621
|
-
...(
|
|
622
|
-
...(position === undefined ||
|
|
627
|
+
...(localX === undefined ? {} : { x: absoluteX }),
|
|
628
|
+
...(localY === undefined ? {} : { y: absoluteY }),
|
|
629
|
+
...(position === undefined || localX !== undefined
|
|
623
630
|
? {}
|
|
624
631
|
: { xExpression: firstValue(position, 'x') ?? '<missing>' }),
|
|
625
|
-
...(position === undefined ||
|
|
632
|
+
...(position === undefined || localY !== undefined
|
|
626
633
|
? {}
|
|
627
634
|
: { yExpression: firstValue(position, 'y') ?? '<missing>' }),
|
|
628
635
|
},
|
|
@@ -645,11 +652,85 @@ function parseTechnologyGridboxes(file, document, fragment, signal) {
|
|
|
645
652
|
};
|
|
646
653
|
fragment.gridboxes.push(gridbox);
|
|
647
654
|
}
|
|
655
|
+
if (elementType === 'containerwindowtype' && name !== undefined) {
|
|
656
|
+
const item = technologyItemLayoutName(name.value);
|
|
657
|
+
if (item !== undefined) {
|
|
658
|
+
const size = childBlocks(block, 'size')[0];
|
|
659
|
+
const width = numericLiteral(size === undefined ? undefined : firstValue(size, 'width'));
|
|
660
|
+
const height = numericLiteral(size === undefined ? undefined : firstValue(size, 'height'));
|
|
661
|
+
const layout = {
|
|
662
|
+
id: deterministicId('tech-item-layout', {
|
|
663
|
+
name: name.value,
|
|
664
|
+
path: file.displayPath,
|
|
665
|
+
offset: assignment.start,
|
|
666
|
+
}),
|
|
667
|
+
name: name.value,
|
|
668
|
+
folderId: item.folderId,
|
|
669
|
+
layoutSize: item.layoutSize,
|
|
670
|
+
position: {
|
|
671
|
+
...(localX === undefined ? {} : { x: localX }),
|
|
672
|
+
...(localY === undefined ? {} : { y: localY }),
|
|
673
|
+
...(position === undefined || localX !== undefined
|
|
674
|
+
? {}
|
|
675
|
+
: { xExpression: firstValue(position, 'x') ?? '<missing>' }),
|
|
676
|
+
...(position === undefined || localY !== undefined
|
|
677
|
+
? {}
|
|
678
|
+
: { yExpression: firstValue(position, 'y') ?? '<missing>' }),
|
|
679
|
+
},
|
|
680
|
+
size: {
|
|
681
|
+
...(width === undefined ? {} : { width }),
|
|
682
|
+
...(height === undefined ? {} : { height }),
|
|
683
|
+
...(size === undefined || width !== undefined
|
|
684
|
+
? {}
|
|
685
|
+
: { widthExpression: firstValue(size, 'width') ?? '<missing>' }),
|
|
686
|
+
...(size === undefined || height !== undefined
|
|
687
|
+
? {}
|
|
688
|
+
: { heightExpression: firstValue(size, 'height') ?? '<missing>' }),
|
|
689
|
+
},
|
|
690
|
+
location: nodeLocation(document, assignment, name.value),
|
|
691
|
+
sourcePath: file.displayPath,
|
|
692
|
+
loadOrder: file.loadOrder,
|
|
693
|
+
};
|
|
694
|
+
fragment.itemLayouts.push(layout);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (elementType === 'instanttextboxtype' && name !== undefined && folderId !== undefined) {
|
|
698
|
+
const text = firstValue(block, 'text');
|
|
699
|
+
const year = text === undefined
|
|
700
|
+
? undefined
|
|
701
|
+
: /^(?:18|19|20)\d{2}$/u.test(text)
|
|
702
|
+
? Number(text)
|
|
703
|
+
: undefined;
|
|
704
|
+
if (year !== undefined && localX !== undefined && localY !== undefined) {
|
|
705
|
+
const marker = {
|
|
706
|
+
id: deterministicId('tech-year-marker', {
|
|
707
|
+
name: name.value,
|
|
708
|
+
year,
|
|
709
|
+
path: file.displayPath,
|
|
710
|
+
offset: assignment.start,
|
|
711
|
+
}),
|
|
712
|
+
name: name.value,
|
|
713
|
+
folderId,
|
|
714
|
+
year,
|
|
715
|
+
position: { x: absoluteX, y: absoluteY },
|
|
716
|
+
location: nodeLocation(document, assignment, name.value),
|
|
717
|
+
sourcePath: file.displayPath,
|
|
718
|
+
loadOrder: file.loadOrder,
|
|
719
|
+
};
|
|
720
|
+
fragment.yearMarkers.push(marker);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
648
723
|
const namedContainers = name !== undefined &&
|
|
649
724
|
(elementType === 'containerwindowtype' || elementType === 'windowtype')
|
|
650
725
|
? [...current.namedContainers, name.value]
|
|
651
726
|
: current.namedContainers;
|
|
652
|
-
|
|
727
|
+
const positionedContainer = elementType === 'containerwindowtype' || elementType === 'windowtype';
|
|
728
|
+
stack.push({
|
|
729
|
+
block,
|
|
730
|
+
namedContainers,
|
|
731
|
+
offsetX: positionedContainer ? absoluteX : current.offsetX,
|
|
732
|
+
offsetY: positionedContainer ? absoluteY : current.offsetY,
|
|
733
|
+
});
|
|
653
734
|
}
|
|
654
735
|
}
|
|
655
736
|
}
|
|
@@ -913,6 +994,8 @@ export function analyzeTechnologySource(file, context) {
|
|
|
913
994
|
folders: [],
|
|
914
995
|
placements: [],
|
|
915
996
|
gridboxes: [],
|
|
997
|
+
itemLayouts: [],
|
|
998
|
+
yearMarkers: [],
|
|
916
999
|
edges: [],
|
|
917
1000
|
categories: [],
|
|
918
1001
|
doctrineDefinitions: [],
|
|
@@ -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
|
+
export const PACKAGE_VERSION = '3.0.1';
|
|
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/development.md
CHANGED
|
@@ -35,4 +35,6 @@ npm run test:local
|
|
|
35
35
|
|
|
36
36
|
Add `HOI4_DEPENDENCY_ROOTS` as the platform path-delimited list when the external mod has dependencies. Local tests remain read-only.
|
|
37
37
|
|
|
38
|
+
The installed-game qualification also parses every shipped bitmap-font descriptor, resolves every atlas page, shapes native and enlarged glyphs, verifies deterministic raster output, and checks HOI4 localisation colours across base fonts and language overrides.
|
|
39
|
+
|
|
38
40
|
See the package [Security Policy](../SECURITY.md) for private vulnerability reporting.
|
package/docs/gui.md
CHANGED
|
@@ -31,13 +31,13 @@ Rewrite once, then inspect and render the result.
|
|
|
31
31
|
|
|
32
32
|
Inspection checks missing assets and localisation, invalid sizes, overlap, clipping, overflow, button-label centering, content crossing a background boundary, conflicting click regions, invisible blockers, broken parents or contexts, list-row cuts, state conflicts, trigger/effect gaps, and resolution drift. `hoi4.gui_render` returns a scripted-scenario gallery and JSON delta report alongside the generic state and resolution galleries.
|
|
33
33
|
|
|
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.
|
|
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, country flags, and localisation icons such as `£command_power` without copying game assets into the mod. Case differences in GFX and font identifiers do not prevent a valid asset from resolving.
|
|
35
35
|
|
|
36
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. The renderer reads BMFont channel roles, separates face and outline coverage, and recognizes both vanilla black-on-white masks and conventional white-on-transparent custom masks. HOI4 localisation colour runs such as `§Y` and `§R` therefore colour the glyph face while retaining its sharp antialiasing and declared border instead of flattening the glyph into a blurred monochrome mask or switching to the tool font. The default language uses the base font rather than an unrelated language override, and the renderer recognizes installed-game atlases whose filename omits a page suffix declared by the matching `.fnt` file.
|
|
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
|
-
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
|
|
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 text is parsed after scenario and scripted-localisation substitution, so a runtime string containing `£command_power` or `£[GetResourceIcon]` renders the resolved inline HOI4 text icon instead of literal marker text. 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`, provide `countryTag` and `ideology` for `GetFlag`, and supply row-scoped text, image, frame, position, visibility, and enabled values. Masked country sprites use the actual mask and overlay dimensions, so custom flag sizes are discovered from GFX assets rather than assumed by the renderer.
|
|
41
41
|
|
|
42
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
43
|
|
|
@@ -66,11 +66,22 @@ Preview scenarios can include exact layout expectations:
|
|
|
66
66
|
"values": {
|
|
67
67
|
"threat": 73,
|
|
68
68
|
"GetThreatLabel": "High",
|
|
69
|
+
"GetCostLine": "Cost £command_power 20",
|
|
69
70
|
"threat_meter": 73,
|
|
70
71
|
"confirm_button.enabled": true
|
|
71
72
|
},
|
|
72
73
|
"flags": { "panel_open": true },
|
|
73
|
-
"lists": {
|
|
74
|
+
"lists": {
|
|
75
|
+
"target_list": [
|
|
76
|
+
{
|
|
77
|
+
"id": 1,
|
|
78
|
+
"label": "France",
|
|
79
|
+
"countryScope": true,
|
|
80
|
+
"countryTag": "FRA",
|
|
81
|
+
"ideology": "democratic"
|
|
82
|
+
}
|
|
83
|
+
]
|
|
84
|
+
},
|
|
74
85
|
"visibility": { "target_list": true, "confirm_button": true },
|
|
75
86
|
"expectations": {
|
|
76
87
|
"visible": ["target_list", "confirm_button"],
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/docs/probability.md
CHANGED
|
@@ -16,7 +16,9 @@ Use them for event timing and option weights, decision and mission scores, focus
|
|
|
16
16
|
|
|
17
17
|
All seven tools are read-only. Proposed source is parsed in memory and never written. When an installed game root is configured, evaluation fails closed unless `launcher-settings.json` identifies the supported HOI4 build and checksum. Results otherwise state that they target the adapter version without claiming local-game verification.
|
|
18
18
|
|
|
19
|
-
Pass a source without an adapter when the surface type is not yet known. Inspection returns every compatible adapter, candidate counts, and example candidate IDs. If a requested adapter, identifier, or candidate pool does not match the source, inspection returns the same discovery result with an explanation and suggested adapter instead of `PROBABILITY_SURFACE_EMPTY
|
|
19
|
+
Pass a source without an adapter when the surface type is not yet known. Inspection returns every compatible adapter, candidate counts, and example candidate IDs. If a requested adapter, identifier, or candidate pool does not match the source, inspection returns the same discovery result with an explanation and suggested adapter instead of `PROBABILITY_SURFACE_EMPTY`.
|
|
20
|
+
|
|
21
|
+
Evaluation, sweep, simulation, and source comparison also accept an omitted adapter. When a source selector identifies one unambiguous weighted surface, the analyzer selects that source-backed adapter automatically. If a caller supplies a mismatched adapter or `custom_weighted_pool` for a source-backed request, the analyzer corrects it when the source has exactly one match. `customPoolManifest`, `beforeManifest`, and `afterManifest` use the custom-pool adapter automatically; `PROBABILITY_SURFACE_EMPTY` is reserved for sources with no weighted surface, while genuinely multi-adapter sources ask the caller to narrow by identifier or line.
|
|
20
22
|
|
|
21
23
|
## Scenarios
|
|
22
24
|
|
package/docs/technology.md
CHANGED
|
@@ -49,7 +49,7 @@ Game-backed and otherwise large workspaces automatically use focused analysis mo
|
|
|
49
49
|
|
|
50
50
|
## Render
|
|
51
51
|
|
|
52
|
-
`hoi4.tech_render` returns authoritative JSON with deterministic SVG and PNG resources; set `includeHtml` when a bundled static report is useful. Available views are `summary`, `folder`, `dependencies`, `technology`, `doctrine`, `exclusive`, `memberships`, `bonuses`, `grants`, `unlocks`, `metadata`, `assets`, and `unresolved`.
|
|
52
|
+
`hoi4.tech_render` returns authoritative JSON with deterministic SVG and PNG resources; set `includeHtml` when a bundled static report is useful. Folder renders read the actual `techtree_<folder>_item` and `techtree_<folder>_small_item` geometry, choose the small item for technologies that do not unlock equipment or explicitly force it, and include the source GUI's horizontal or vertical year guide. Available views are `summary`, `folder`, `dependencies`, `technology`, `doctrine`, `exclusive`, `memberships`, `bonuses`, `grants`, `unlocks`, `metadata`, `assets`, and `unresolved`.
|
|
53
53
|
|
|
54
54
|
Folder renders use the actual folder assignments, gridbox geometry, technology coordinates, and HOI4 item size found in source. Technologies with `force_use_small_tech_layout = yes` render as compact icon items; normal technologies render as large items with their icon and label. Sprite declarations and texture files resolve through the active load order across the mod, configured dependencies, and the detected or configured game installation, so vanilla icons used by a mod appear in SVG, PNG, and HTML artifacts. The authoritative JSON and SVG expose `layoutSize`, rendered-icon coverage, and unresolved sprite names without embedding image data in JSON. Dependency and other semantic views are explicitly labelled as generated analysis layouts.
|
|
55
55
|
|
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": "
|
|
4
|
+
"version": "3.0.1",
|
|
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",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"docs/map.md",
|
|
48
48
|
"docs/http.md",
|
|
49
49
|
"docs/development.md",
|
|
50
|
+
"docs/images/readme/",
|
|
50
51
|
"examples/clients/",
|
|
51
52
|
"examples/probability/",
|
|
52
53
|
"schemas/",
|
|
@@ -75,6 +76,7 @@
|
|
|
75
76
|
"test:local": "tsx scripts/run-local-tests.ts",
|
|
76
77
|
"test:coverage": "vitest run --coverage",
|
|
77
78
|
"benchmark": "node --expose-gc --import tsx scripts/benchmark.ts",
|
|
79
|
+
"docs:examples": "tsx scripts/generate-readme-examples.ts",
|
|
78
80
|
"fixtures:generate": "npm run fixtures:generate:focus && npm run fixtures:generate:gui && npm run fixtures:generate:map && npm run fixtures:generate:event && npm run fixtures:generate:technology && npm run fixtures:generate:probability",
|
|
79
81
|
"fixtures:generate:focus": "tsx scripts/generate-fixtures.ts",
|
|
80
82
|
"fixtures:generate:gui": "tsx scripts/generate-gui-fixture.ts",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/schemas/configuration.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
|
@@ -60,13 +60,13 @@
|
|
|
60
60
|
"minLength": 1
|
|
61
61
|
},
|
|
62
62
|
"__schema2": {
|
|
63
|
-
"default":
|
|
63
|
+
"default": 100000,
|
|
64
64
|
"type": "integer",
|
|
65
65
|
"minimum": 1,
|
|
66
66
|
"maximum": 1000000
|
|
67
67
|
},
|
|
68
68
|
"__schema3": {
|
|
69
|
-
"default":
|
|
69
|
+
"default": 268435456,
|
|
70
70
|
"type": "integer",
|
|
71
71
|
"minimum": 1048576,
|
|
72
72
|
"maximum": 9007199254740991
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/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/
|
|
2
|
+
"$id": "https://github.com/klimPaskov/hoi4-agent-tools/blob/v3.0.1/schemas/gui-helper.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|