hoi4-agent-tools 2.8.6 → 2.8.8
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/dist/hoi4_agent_tools/core/artifacts.js +103 -42
- package/dist/hoi4_agent_tools/gui/assets.js +33 -12
- package/dist/hoi4_agent_tools/gui/layout.js +5 -5
- package/dist/hoi4_agent_tools/gui/renderer.js +3 -3
- package/dist/hoi4_agent_tools/gui/scenario-generator.js +70 -9
- package/dist/hoi4_agent_tools/gui/source-graph.js +39 -16
- package/dist/hoi4_agent_tools/probability/service.js +3 -2
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/gui.md +2 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,17 @@
|
|
|
4
4
|
|
|
5
5
|
No unreleased changes.
|
|
6
6
|
|
|
7
|
+
## 2.8.8 - 2026-08-29
|
|
8
|
+
|
|
9
|
+
- Correct GUI font selection so mod definitions override lower-load-order game definitions, language-specific overrides only apply to their language, and extensionless WOFF assets resolve.
|
|
10
|
+
- Accept numeric-block and six-digit Paradox colors, normalize fractional text-color channels, and place color runs from kerning-aware measurements without clipping glyph overhangs.
|
|
11
|
+
- Make generated GUI scenarios favour ordinary values, respect literal numeric limits and progress ranges, and keep current/limit pairs coherent.
|
|
12
|
+
|
|
13
|
+
## 2.8.7 - 2026-08-28
|
|
14
|
+
|
|
15
|
+
- Isolate stale, foreign, oversized, malformed, and address-mismatched artifact manifests so one bad generated record cannot block unrelated probability, event, GUI, or other MCP evidence routes.
|
|
16
|
+
- Repair corrupted deterministic manifests from recomputed expected bytes, validate cached probability resources before reuse, and keep direct foreign-artifact access unavailable without exposing cross-workspace content.
|
|
17
|
+
|
|
7
18
|
## 2.8.6 - 2026-08-28
|
|
8
19
|
|
|
9
20
|
- Generate source-aware scripted-GUI scenarios from complete parent/child surfaces, real scripted-localisation candidates, simple threshold conditions, coherent tab states, scoped values, meter ranges, and dynamic-list rows while keeping explicit scenario values authoritative.
|
|
@@ -132,11 +132,14 @@ export function boundedSourceHashEvidence(sourceHashes) {
|
|
|
132
132
|
}
|
|
133
133
|
function assertManifestIntegrity(manifest, expected, workspace) {
|
|
134
134
|
assertManifestAddressIntegrity(manifest, expected);
|
|
135
|
-
if (manifest
|
|
136
|
-
manifest
|
|
137
|
-
throw new ServiceError('ARTIFACT_MANIFEST_INTEGRITY_FAILED', 'Artifact provenance manifest does not match its immutable address');
|
|
135
|
+
if (!manifestBelongsToWorkspace(manifest, workspace)) {
|
|
136
|
+
throw new ServiceError('ARTIFACT_NOT_FOUND', 'Artifact provenance manifest is unavailable');
|
|
138
137
|
}
|
|
139
138
|
}
|
|
139
|
+
function manifestBelongsToWorkspace(manifest, workspace) {
|
|
140
|
+
return (manifest.workspaceIdentity === workspace.workspaceIdentity &&
|
|
141
|
+
manifest.ownerIdentity === workspace.ownerIdentity);
|
|
142
|
+
}
|
|
140
143
|
function assertManifestAddressIntegrity(manifest, expected) {
|
|
141
144
|
if (manifest.sha256 !== expected.sha256 ||
|
|
142
145
|
manifest.provenanceHash !== expected.provenanceHash ||
|
|
@@ -145,6 +148,33 @@ function assertManifestAddressIntegrity(manifest, expected) {
|
|
|
145
148
|
throw new ServiceError('ARTIFACT_MANIFEST_INTEGRITY_FAILED', 'Artifact provenance manifest does not match its immutable address');
|
|
146
149
|
}
|
|
147
150
|
}
|
|
151
|
+
function recoverableManifestError(error) {
|
|
152
|
+
return (error instanceof ServiceError &&
|
|
153
|
+
[
|
|
154
|
+
'ARTIFACT_NOT_FOUND',
|
|
155
|
+
'ARTIFACT_MANIFEST_INVALID',
|
|
156
|
+
'ARTIFACT_MANIFEST_LIMIT',
|
|
157
|
+
'ARTIFACT_MANIFEST_INTEGRITY_FAILED',
|
|
158
|
+
].includes(error.code));
|
|
159
|
+
}
|
|
160
|
+
async function enumerableArtifactManifest(workspace, directory, entryName, signal) {
|
|
161
|
+
try {
|
|
162
|
+
const manifest = await readArtifactManifest(path.join(directory, entryName), signal);
|
|
163
|
+
if (entryName !== `${manifest.name}.${manifest.provenanceHash}.manifest.json`)
|
|
164
|
+
return undefined;
|
|
165
|
+
assertManifestAddressIntegrity(manifest, {
|
|
166
|
+
sha256: path.basename(directory),
|
|
167
|
+
provenanceHash: manifest.provenanceHash,
|
|
168
|
+
name: manifest.name,
|
|
169
|
+
});
|
|
170
|
+
return manifestBelongsToWorkspace(manifest, workspace) ? manifest : undefined;
|
|
171
|
+
}
|
|
172
|
+
catch (error) {
|
|
173
|
+
if (recoverableManifestError(error))
|
|
174
|
+
return undefined;
|
|
175
|
+
throw error;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
148
178
|
/** Return the only artifact fields that may cross the MCP trust boundary. */
|
|
149
179
|
export function publicArtifactLink(artifact) {
|
|
150
180
|
return {
|
|
@@ -501,6 +531,23 @@ async function existingManifestMatches(filePath, expectedBytes, signal) {
|
|
|
501
531
|
try {
|
|
502
532
|
const manifest = await readArtifactManifest(filePath, signal);
|
|
503
533
|
if (`${canonicalJson(manifest)}\n` !== expectedBytes) {
|
|
534
|
+
const expected = artifactManifestSchema.parse(JSON.parse(expectedBytes));
|
|
535
|
+
try {
|
|
536
|
+
assertManifestAddressIntegrity(manifest, {
|
|
537
|
+
sha256: expected.sha256,
|
|
538
|
+
provenanceHash: expected.provenanceHash,
|
|
539
|
+
name: expected.name,
|
|
540
|
+
});
|
|
541
|
+
}
|
|
542
|
+
catch (error) {
|
|
543
|
+
if (!recoverableManifestError(error))
|
|
544
|
+
throw error;
|
|
545
|
+
await unlink(filePath).catch((unlinkError) => {
|
|
546
|
+
if (unlinkError.code !== 'ENOENT')
|
|
547
|
+
throw unlinkError;
|
|
548
|
+
});
|
|
549
|
+
return false;
|
|
550
|
+
}
|
|
504
551
|
throw new ServiceError('ARTIFACT_MANIFEST_INTEGRITY_FAILED', 'Immutable artifact provenance manifest contains different bytes');
|
|
505
552
|
}
|
|
506
553
|
return true;
|
|
@@ -508,11 +555,17 @@ async function existingManifestMatches(filePath, expectedBytes, signal) {
|
|
|
508
555
|
catch (error) {
|
|
509
556
|
if (error instanceof ServiceError && error.code === 'ARTIFACT_NOT_FOUND')
|
|
510
557
|
return false;
|
|
511
|
-
if (!(error instanceof ServiceError) ||
|
|
558
|
+
if (!(error instanceof ServiceError) ||
|
|
559
|
+
!['ARTIFACT_MANIFEST_INVALID', 'ARTIFACT_MANIFEST_LIMIT'].includes(error.code))
|
|
512
560
|
throw error;
|
|
513
561
|
const state = await interruptedManifestState(filePath, expectedBytes, signal);
|
|
514
|
-
if (!state.interrupted)
|
|
515
|
-
|
|
562
|
+
if (!state.interrupted) {
|
|
563
|
+
await unlink(filePath).catch((unlinkError) => {
|
|
564
|
+
if (unlinkError.code !== 'ENOENT')
|
|
565
|
+
throw unlinkError;
|
|
566
|
+
});
|
|
567
|
+
return false;
|
|
568
|
+
}
|
|
516
569
|
try {
|
|
517
570
|
await unlink(filePath);
|
|
518
571
|
}
|
|
@@ -689,19 +742,31 @@ export class ArtifactStore {
|
|
|
689
742
|
catch (error) {
|
|
690
743
|
if (error instanceof ServiceError && error.code === 'ARTIFACT_NOT_FOUND')
|
|
691
744
|
continue;
|
|
692
|
-
if (!(error
|
|
745
|
+
if (!recoverableManifestError(error))
|
|
693
746
|
throw error;
|
|
694
|
-
let interrupted;
|
|
695
747
|
try {
|
|
696
|
-
|
|
748
|
+
await unlink(candidate);
|
|
697
749
|
}
|
|
698
|
-
catch (
|
|
699
|
-
if (
|
|
700
|
-
|
|
701
|
-
continue;
|
|
702
|
-
throw interruptedError;
|
|
750
|
+
catch (unlinkError) {
|
|
751
|
+
if (unlinkError.code !== 'ENOENT')
|
|
752
|
+
throw unlinkError;
|
|
703
753
|
}
|
|
704
|
-
|
|
754
|
+
usage.entries = Math.max(0, usage.entries - 1);
|
|
755
|
+
usage.bytes = Math.max(0, usage.bytes - metadata.size);
|
|
756
|
+
continue;
|
|
757
|
+
}
|
|
758
|
+
try {
|
|
759
|
+
if (child.name !== `${manifest.name}.${manifest.provenanceHash}.manifest.json`) {
|
|
760
|
+
throw new ServiceError('ARTIFACT_MANIFEST_INTEGRITY_FAILED', 'Artifact provenance manifest filename does not match its contents');
|
|
761
|
+
}
|
|
762
|
+
assertManifestAddressIntegrity(manifest, {
|
|
763
|
+
sha256: path.basename(directory),
|
|
764
|
+
provenanceHash: manifest.provenanceHash,
|
|
765
|
+
name: manifest.name,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
catch (error) {
|
|
769
|
+
if (!recoverableManifestError(error))
|
|
705
770
|
throw error;
|
|
706
771
|
try {
|
|
707
772
|
await unlink(candidate);
|
|
@@ -711,17 +776,9 @@ export class ArtifactStore {
|
|
|
711
776
|
throw unlinkError;
|
|
712
777
|
}
|
|
713
778
|
usage.entries = Math.max(0, usage.entries - 1);
|
|
714
|
-
usage.bytes = Math.max(0, usage.bytes -
|
|
779
|
+
usage.bytes = Math.max(0, usage.bytes - metadata.size);
|
|
715
780
|
continue;
|
|
716
781
|
}
|
|
717
|
-
if (child.name !== `${manifest.name}.${manifest.provenanceHash}.manifest.json`) {
|
|
718
|
-
throw new ServiceError('ARTIFACT_MANIFEST_INTEGRITY_FAILED', 'Artifact provenance manifest filename does not match its contents');
|
|
719
|
-
}
|
|
720
|
-
assertManifestAddressIntegrity(manifest, {
|
|
721
|
-
sha256: path.basename(directory),
|
|
722
|
-
provenanceHash: manifest.provenanceHash,
|
|
723
|
-
name: manifest.name,
|
|
724
|
-
});
|
|
725
782
|
const targetPath = await containedGeneratedPath(workspace.artifactRoot, path.relative(workspace.artifactRoot, path.join(directory, manifest.name)));
|
|
726
783
|
let targetBytes = 0;
|
|
727
784
|
try {
|
|
@@ -867,15 +924,10 @@ export class ArtifactStore {
|
|
|
867
924
|
await walk(await containedGeneratedPath(workspace.artifactRoot, path.relative(workspace.artifactRoot, candidate)));
|
|
868
925
|
}
|
|
869
926
|
else if (entry.isFile() && entry.name.endsWith('.manifest.json')) {
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
assertManifestIntegrity(manifest, {
|
|
875
|
-
sha256: path.basename(directory),
|
|
876
|
-
provenanceHash: manifest.provenanceHash,
|
|
877
|
-
name: manifest.name,
|
|
878
|
-
}, workspace);
|
|
927
|
+
await containedGeneratedPath(workspace.artifactRoot, path.relative(workspace.artifactRoot, candidate));
|
|
928
|
+
const manifest = await enumerableArtifactManifest(workspace, directory, entry.name, signal);
|
|
929
|
+
if (manifest === undefined)
|
|
930
|
+
continue;
|
|
879
931
|
results.push({
|
|
880
932
|
uri: this.uri(workspace.id, manifest.sha256, manifest.provenanceHash, manifest.name),
|
|
881
933
|
name: manifest.name,
|
|
@@ -908,15 +960,10 @@ export class ArtifactStore {
|
|
|
908
960
|
let hasMore = false;
|
|
909
961
|
const visitManifest = async (directory, entryName) => {
|
|
910
962
|
signal?.throwIfAborted();
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
assertManifestIntegrity(manifest, {
|
|
916
|
-
sha256: path.basename(directory),
|
|
917
|
-
provenanceHash: manifest.provenanceHash,
|
|
918
|
-
name: manifest.name,
|
|
919
|
-
}, workspace);
|
|
963
|
+
await containedGeneratedPath(workspace.artifactRoot, path.relative(workspace.artifactRoot, path.join(directory, entryName)));
|
|
964
|
+
const manifest = await enumerableArtifactManifest(workspace, directory, entryName, signal);
|
|
965
|
+
if (manifest === undefined)
|
|
966
|
+
return;
|
|
920
967
|
const uri = this.uri(workspace.id, manifest.sha256, manifest.provenanceHash, manifest.name);
|
|
921
968
|
revision.update(`${Buffer.byteLength(uri, 'utf8')}:${uri}`);
|
|
922
969
|
total += 1;
|
|
@@ -989,6 +1036,20 @@ export class ArtifactStore {
|
|
|
989
1036
|
path: await containedGeneratedPath(workspace.artifactRoot, parsed.sha256.slice(0, 2), parsed.sha256, parsed.name),
|
|
990
1037
|
};
|
|
991
1038
|
}
|
|
1039
|
+
async available(workspace, artifacts, signal) {
|
|
1040
|
+
for (const artifact of artifacts) {
|
|
1041
|
+
signal?.throwIfAborted();
|
|
1042
|
+
try {
|
|
1043
|
+
await this.describe(workspace, artifact.uri, signal);
|
|
1044
|
+
}
|
|
1045
|
+
catch (error) {
|
|
1046
|
+
if (recoverableManifestError(error))
|
|
1047
|
+
return false;
|
|
1048
|
+
throw error;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
992
1053
|
async put(workspace, name, mimeType, content, provenance, description, signal) {
|
|
993
1054
|
return this.withAtomicWrites(workspace, [
|
|
994
1055
|
{
|
|
@@ -156,6 +156,7 @@ export class GuiAssetCatalog {
|
|
|
156
156
|
constructor(graph, scannedFiles, budget = new RenderBudget(), language = 'l_english') {
|
|
157
157
|
this.graph = graph;
|
|
158
158
|
this.budget = budget;
|
|
159
|
+
const sourceLoadOrder = new Map(scannedFiles.map((file) => [file.displayPath, file.loadOrder]));
|
|
159
160
|
for (const file of scannedFiles
|
|
160
161
|
.filter((candidate) => candidate.shadowedBy === undefined)
|
|
161
162
|
.sort((left, right) => compareCodeUnits(left.relativePath, right.relativePath))) {
|
|
@@ -167,25 +168,38 @@ export class GuiAssetCatalog {
|
|
|
167
168
|
this.basenames.set(basename, candidates);
|
|
168
169
|
}
|
|
169
170
|
const normalizedLanguage = language.toLocaleLowerCase('en-US');
|
|
170
|
-
|
|
171
|
+
const eligibleFonts = graph.fonts.filter((definition) => definition.languages.length === 0 ||
|
|
172
|
+
definition.languages.some((candidate) => candidate.toLocaleLowerCase('en-US') === normalizedLanguage));
|
|
173
|
+
const definitionPriority = (definition) => {
|
|
174
|
+
const languageMatch = definition.languages.some((candidate) => candidate.toLocaleLowerCase('en-US') === normalizedLanguage);
|
|
175
|
+
return [
|
|
176
|
+
languageMatch ? 1 : 0,
|
|
177
|
+
sourceLoadOrder.get(definition.sourcePath) ?? 0,
|
|
178
|
+
languageMatch === definition.override ? 1 : 0,
|
|
179
|
+
];
|
|
180
|
+
};
|
|
181
|
+
const higherPriority = (left, right) => {
|
|
182
|
+
const leftPriority = definitionPriority(left);
|
|
183
|
+
const rightPriority = definitionPriority(right);
|
|
184
|
+
for (let index = 0; index < leftPriority.length; index += 1) {
|
|
185
|
+
if (leftPriority[index] !== rightPriority[index])
|
|
186
|
+
return leftPriority[index] > rightPriority[index];
|
|
187
|
+
}
|
|
188
|
+
return false;
|
|
189
|
+
};
|
|
190
|
+
for (const definition of eligibleFonts) {
|
|
171
191
|
const key = definition.name.toLocaleLowerCase('en-US');
|
|
172
192
|
const existing = this.fontDefinitions.get(key);
|
|
173
|
-
|
|
174
|
-
const existingMatches = existing?.languages.some((candidate) => candidate.toLocaleLowerCase('en-US') === normalizedLanguage) ?? false;
|
|
175
|
-
if (existing === undefined ||
|
|
176
|
-
(definitionMatches && !existingMatches) ||
|
|
177
|
-
(definitionMatches === existingMatches && existing.override && !definition.override) ||
|
|
178
|
-
(definitionMatches === existingMatches &&
|
|
179
|
-
existing.languages.length > 0 &&
|
|
180
|
-
definition.languages.length === 0))
|
|
193
|
+
if (existing === undefined || higherPriority(definition, existing))
|
|
181
194
|
this.fontDefinitions.set(key, definition);
|
|
182
195
|
}
|
|
183
196
|
const baseDefinitions = new Map();
|
|
184
|
-
for (const definition of
|
|
197
|
+
for (const definition of eligibleFonts) {
|
|
185
198
|
if (definition.override || definition.languages.length > 0)
|
|
186
199
|
continue;
|
|
187
200
|
const key = definition.name.toLocaleLowerCase('en-US');
|
|
188
|
-
|
|
201
|
+
const existing = baseDefinitions.get(key);
|
|
202
|
+
if (existing === undefined || higherPriority(definition, existing))
|
|
189
203
|
baseDefinitions.set(key, definition);
|
|
190
204
|
}
|
|
191
205
|
for (const [key, definition] of this.fontDefinitions) {
|
|
@@ -211,7 +225,14 @@ export class GuiAssetCatalog {
|
|
|
211
225
|
const extension = path.posix.extname(normalized);
|
|
212
226
|
const rasterExtensions = ['.png', '.bmp', '.tga', '.dds', '.svg'];
|
|
213
227
|
const normalizedCandidates = extension.length === 0
|
|
214
|
-
? [
|
|
228
|
+
? [
|
|
229
|
+
normalized,
|
|
230
|
+
`${normalized}.fnt`,
|
|
231
|
+
`${normalized}.ttf`,
|
|
232
|
+
`${normalized}.otf`,
|
|
233
|
+
`${normalized}.woff`,
|
|
234
|
+
`${normalized}.woff2`,
|
|
235
|
+
]
|
|
215
236
|
: rasterExtensions.includes(extension)
|
|
216
237
|
? [
|
|
217
238
|
normalized,
|
|
@@ -460,11 +460,9 @@ function colourRunsForLine(catalog, fontName, line, colours, fontSize, work, inl
|
|
|
460
460
|
return [];
|
|
461
461
|
const runs = [];
|
|
462
462
|
let start = 0;
|
|
463
|
-
let offsetX = 0;
|
|
464
463
|
while (start < line.length) {
|
|
465
464
|
const inlineIcon = inlineIcons.get(line[start] ?? '');
|
|
466
465
|
if (inlineIcon !== undefined) {
|
|
467
|
-
offsetX += inlineIcon.width;
|
|
468
466
|
start += 1;
|
|
469
467
|
continue;
|
|
470
468
|
}
|
|
@@ -475,10 +473,12 @@ function colourRunsForLine(catalog, fontName, line, colours, fontSize, work, inl
|
|
|
475
473
|
(colours[end] ?? defaultColour) === colour)
|
|
476
474
|
end += 1;
|
|
477
475
|
const text = line.slice(start, end);
|
|
478
|
-
work.spendTextLayout('localisation colour-run measurement');
|
|
479
|
-
const
|
|
476
|
+
work.spendTextLayout('localisation colour-run prefix measurement');
|
|
477
|
+
const offsetX = measureTextWithInlineIcons(catalog, fontName, line.slice(0, start), fontSize, inlineIcons).width;
|
|
478
|
+
work.spendTextLayout('localisation colour-run end measurement');
|
|
479
|
+
const endX = measureTextWithInlineIcons(catalog, fontName, line.slice(0, end), fontSize, inlineIcons).width;
|
|
480
|
+
const width = Math.max(0, endX - offsetX);
|
|
480
481
|
runs.push({ text, colour, offsetX, width });
|
|
481
|
-
offsetX += width;
|
|
482
482
|
start = end;
|
|
483
483
|
}
|
|
484
484
|
return runs;
|
|
@@ -137,15 +137,15 @@ function renderText(element, toolText) {
|
|
|
137
137
|
const key = `${element.id}:${index}:${runIndex}:${run.colour}`;
|
|
138
138
|
const clipId = `gui-font-run-clip-${sha256Bytes(key).slice(0, 20)}`;
|
|
139
139
|
const runX = originX + run.offsetX * horizontalScale;
|
|
140
|
-
definitions.push(`<clipPath id="${clipId}"><rect x="${finite(runX - 0.25)}" y="${finite(lineTop)}" width="${finite(run.width * horizontalScale + 0.5)}" height="${finite(text.lineHeight)}"/></clipPath>`);
|
|
140
|
+
definitions.push(`<clipPath id="${clipId}"><rect x="${finite(runX - 0.25)}" y="${finite(lineTop - text.lineHeight)}" width="${finite(run.width * horizontalScale + 0.5)}" height="${finite(text.lineHeight * 3)}"/></clipPath>`);
|
|
141
141
|
const glyphs = actualGlyphMarkup(glyphLine, originX, baseline, horizontalScale);
|
|
142
142
|
if (glyphLine.source === 'bmfont-atlas') {
|
|
143
143
|
const maskId = `gui-font-mask-${sha256Bytes(key).slice(0, 20)}`;
|
|
144
144
|
const runRect = {
|
|
145
145
|
x: runX - 0.25,
|
|
146
|
-
y: lineTop,
|
|
146
|
+
y: lineTop - text.lineHeight,
|
|
147
147
|
width: run.width * horizontalScale + 0.5,
|
|
148
|
-
height: text.lineHeight,
|
|
148
|
+
height: text.lineHeight * 3,
|
|
149
149
|
};
|
|
150
150
|
return `<g clip-path="url(#${clipId})" data-font-colour="${run.colour}">${bitmapTintMarkup(maskId, run.colour, glyphs, runRect)}</g>`;
|
|
151
151
|
}
|
|
@@ -52,6 +52,9 @@ class ScenarioRandom {
|
|
|
52
52
|
integer(minimum, maximum) {
|
|
53
53
|
return Math.floor(this.next() * (maximum - minimum + 1)) + minimum;
|
|
54
54
|
}
|
|
55
|
+
ordinary() {
|
|
56
|
+
return (this.next() + this.next() + this.next()) / 3;
|
|
57
|
+
}
|
|
55
58
|
pick(values) {
|
|
56
59
|
return values[Math.min(values.length - 1, Math.floor(this.next() * values.length))];
|
|
57
60
|
}
|
|
@@ -182,18 +185,64 @@ function plausibleText(key, random, samples, scopedCountries) {
|
|
|
182
185
|
function generatedNumber(options, random, minimum = options.numericMinimum, maximum = options.numericMaximum) {
|
|
183
186
|
const boundedMinimum = Math.max(options.numericMinimum, minimum);
|
|
184
187
|
const boundedMaximum = Math.min(options.numericMaximum, maximum);
|
|
185
|
-
const low =
|
|
186
|
-
|
|
187
|
-
|
|
188
|
+
const [low, high] = boundedMinimum <= boundedMaximum
|
|
189
|
+
? [boundedMinimum, boundedMaximum]
|
|
190
|
+
: maximum < options.numericMinimum
|
|
191
|
+
? [maximum, maximum]
|
|
192
|
+
: [minimum, minimum];
|
|
193
|
+
const value = low + random.ordinary() * (high - low);
|
|
188
194
|
return options.integerValues ? Math.round(value) : Math.round(value * 100) / 100;
|
|
189
195
|
}
|
|
190
|
-
function
|
|
196
|
+
function sourceNumericConstraints(texts) {
|
|
197
|
+
const ranges = new Map();
|
|
198
|
+
const relationships = [];
|
|
199
|
+
const update = (key, range) => {
|
|
200
|
+
const current = ranges.get(key) ?? {};
|
|
201
|
+
const minimum = range.minimum === undefined
|
|
202
|
+
? current.minimum
|
|
203
|
+
: current.minimum === undefined
|
|
204
|
+
? range.minimum
|
|
205
|
+
: Math.max(current.minimum, range.minimum);
|
|
206
|
+
const maximum = range.maximum === undefined
|
|
207
|
+
? current.maximum
|
|
208
|
+
: current.maximum === undefined
|
|
209
|
+
? range.maximum
|
|
210
|
+
: Math.min(current.maximum, range.maximum);
|
|
211
|
+
ranges.set(key, {
|
|
212
|
+
...(minimum === undefined ? {} : { minimum }),
|
|
213
|
+
...(maximum === undefined ? {} : { maximum }),
|
|
214
|
+
});
|
|
215
|
+
};
|
|
216
|
+
const token = String.raw `\[\?([^\]|]+)(?:\|[^\]]+)?\]`;
|
|
217
|
+
for (const text of texts) {
|
|
218
|
+
for (const match of text.matchAll(new RegExp(`${token}\\s*(?:/|of)\\s*(-?(?:\\d+\\.?\\d*|\\.\\d+))`, 'giu'))) {
|
|
219
|
+
const key = match[1];
|
|
220
|
+
const maximum = Number(match[2]);
|
|
221
|
+
if (key !== undefined && Number.isFinite(maximum))
|
|
222
|
+
update(key, { maximum });
|
|
223
|
+
}
|
|
224
|
+
for (const match of text.matchAll(new RegExp(`(-?(?:\\d+\\.?\\d*|\\.\\d+))\\s*(?:-|to)\\s*${token}`, 'giu'))) {
|
|
225
|
+
const minimum = Number(match[1]);
|
|
226
|
+
const key = match[2];
|
|
227
|
+
if (key !== undefined && Number.isFinite(minimum))
|
|
228
|
+
update(key, { minimum });
|
|
229
|
+
}
|
|
230
|
+
for (const match of text.matchAll(new RegExp(`${token}\\s*(?:/|of)\\s*${token}`, 'giu'))) {
|
|
231
|
+
const value = match[1];
|
|
232
|
+
const limit = match[2];
|
|
233
|
+
if (value !== undefined && limit !== undefined)
|
|
234
|
+
relationships.push({ value, limit });
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return { ranges, relationships };
|
|
238
|
+
}
|
|
239
|
+
function generatedNumberForKey(key, options, random, inferred = {}) {
|
|
191
240
|
const lower = key.toLocaleLowerCase('en-US');
|
|
192
241
|
if (/(?:ratio|fraction|progress)$/u.test(lower))
|
|
193
|
-
return generatedNumber(options, random, 0, 1);
|
|
242
|
+
return generatedNumber(options, random, inferred.minimum ?? 0, inferred.maximum ?? 1);
|
|
194
243
|
if (/(?:percent|percentage|support|stability|war_support|legitimacy|threat|charge)/u.test(lower))
|
|
195
|
-
return generatedNumber(options, random, 0, 100);
|
|
196
|
-
return generatedNumber(options, random);
|
|
244
|
+
return generatedNumber(options, random, inferred.minimum ?? 0, inferred.maximum ?? 100);
|
|
245
|
+
return generatedNumber(options, random, inferred.minimum ?? options.numericMinimum, inferred.maximum ?? options.numericMaximum);
|
|
197
246
|
}
|
|
198
247
|
function setIfMissing(target, key, value) {
|
|
199
248
|
target[key] ??= value;
|
|
@@ -419,7 +468,9 @@ export function generateGuiPreviewScenarios(graph, windowName, base, options) {
|
|
|
419
468
|
return [];
|
|
420
469
|
const scripted = relatedScriptedGuis(graph, windowName);
|
|
421
470
|
const elements = relevantElements(graph, windowName, scripted);
|
|
422
|
-
const
|
|
471
|
+
const relevantSourceTexts = sourceTexts(graph, elements, base.language, scripted);
|
|
472
|
+
const tokens = tokensIn(relevantSourceTexts);
|
|
473
|
+
const numericConstraints = sourceNumericConstraints(relevantSourceTexts);
|
|
423
474
|
const imageTokens = new Set();
|
|
424
475
|
const booleanTokens = new Set();
|
|
425
476
|
const frameTokens = new Set();
|
|
@@ -482,7 +533,7 @@ export function generateGuiPreviewScenarios(graph, windowName, base, options) {
|
|
|
482
533
|
const elementStates = { ...base.elementStates };
|
|
483
534
|
const scopedCountries = new Map();
|
|
484
535
|
for (const key of tokens.numeric)
|
|
485
|
-
setIfMissing(values, key, generatedNumberForKey(key, options, random));
|
|
536
|
+
setIfMissing(values, key, generatedNumberForKey(key, options, random, numericConstraints.ranges.get(key)));
|
|
486
537
|
for (const [elementName, { minimum, maximum }] of progressRanges) {
|
|
487
538
|
const element = elements.find(({ name }) => name === elementName);
|
|
488
539
|
const startValueToken = typeof element?.attributes.startValue === 'string'
|
|
@@ -507,6 +558,16 @@ export function generateGuiPreviewScenarios(graph, windowName, base, options) {
|
|
|
507
558
|
if (typeof sourceValue === 'number')
|
|
508
559
|
values[key] = sourceValue;
|
|
509
560
|
}
|
|
561
|
+
for (const { value: valueKey, limit: limitKey } of numericConstraints.relationships) {
|
|
562
|
+
if (base.values[valueKey] !== undefined)
|
|
563
|
+
continue;
|
|
564
|
+
const value = values[valueKey];
|
|
565
|
+
const limit = values[limitKey];
|
|
566
|
+
if (typeof value !== 'number' || typeof limit !== 'number')
|
|
567
|
+
continue;
|
|
568
|
+
const minimum = Math.min(numericConstraints.ranges.get(valueKey)?.minimum ?? options.numericMinimum, limit);
|
|
569
|
+
values[valueKey] = Math.max(minimum, Math.min(value, limit));
|
|
570
|
+
}
|
|
510
571
|
for (const key of tokens.textual)
|
|
511
572
|
setIfMissing(values, key, plausibleText(key, random, options.textSamples, scopedCountries));
|
|
512
573
|
const scriptedTextTemplates = new Map();
|
|
@@ -208,15 +208,44 @@ function boolScalar(block, ...keys) {
|
|
|
208
208
|
return false;
|
|
209
209
|
return undefined;
|
|
210
210
|
}
|
|
211
|
+
function colourChannels(value) {
|
|
212
|
+
if (value === undefined)
|
|
213
|
+
return undefined;
|
|
214
|
+
const channels = value.entries
|
|
215
|
+
.filter((entry) => entry.type === 'scalar')
|
|
216
|
+
.map((entry) => Number(entry.value));
|
|
217
|
+
if (channels.length < 3 || channels.slice(0, 4).some((channel) => !Number.isFinite(channel)))
|
|
218
|
+
return undefined;
|
|
219
|
+
const normalized = channels.slice(0, 4).every((channel) => channel >= 0 && channel <= 1);
|
|
220
|
+
const bytes = channels
|
|
221
|
+
.slice(0, 4)
|
|
222
|
+
.map((channel) => Math.max(0, Math.min(255, Math.round(channel * (normalized ? 255 : 1)))));
|
|
223
|
+
while (bytes.length < 4)
|
|
224
|
+
bytes.push(255);
|
|
225
|
+
return `#${bytes.map((channel) => channel.toString(16).padStart(2, '0')).join('')}`;
|
|
226
|
+
}
|
|
211
227
|
function paradoxColour(value) {
|
|
212
228
|
if (value === undefined)
|
|
213
229
|
return undefined;
|
|
214
|
-
const match = /^0x([0-9a-f]{8})$/iu.exec(value.trim());
|
|
230
|
+
const match = /^0x([0-9a-f]{6}|[0-9a-f]{8})$/iu.exec(value.trim());
|
|
215
231
|
if (match === null)
|
|
216
232
|
return undefined;
|
|
233
|
+
if (match[1].length === 6)
|
|
234
|
+
return `#${match[1].toLowerCase()}ff`;
|
|
217
235
|
const argb = match[1];
|
|
218
236
|
return `#${argb.slice(2)}${argb.slice(0, 2)}`.toLowerCase();
|
|
219
237
|
}
|
|
238
|
+
function colourFrom(block, ...keys) {
|
|
239
|
+
const normalized = new Set(keys.map((key) => key.toLowerCase()));
|
|
240
|
+
for (const assignment of assignments(block)) {
|
|
241
|
+
if (!normalized.has(assignment.key.value.toLowerCase()))
|
|
242
|
+
continue;
|
|
243
|
+
if (assignment.value.type === 'scalar')
|
|
244
|
+
return paradoxColour(assignment.value.value);
|
|
245
|
+
return colourChannels(assignment.value);
|
|
246
|
+
}
|
|
247
|
+
return undefined;
|
|
248
|
+
}
|
|
220
249
|
function textColoursFrom(block) {
|
|
221
250
|
const textColours = childBlocks(block, 'textcolors')[0];
|
|
222
251
|
if (textColours === undefined)
|
|
@@ -225,15 +254,9 @@ function textColoursFrom(block) {
|
|
|
225
254
|
for (const entry of assignments(textColours)) {
|
|
226
255
|
if (entry.value.type !== 'block')
|
|
227
256
|
continue;
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
.
|
|
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')}`;
|
|
257
|
+
const colour = colourChannels(entry.value);
|
|
258
|
+
if (colour !== undefined)
|
|
259
|
+
result[entry.key.value] = colour.slice(0, 7);
|
|
237
260
|
}
|
|
238
261
|
return result;
|
|
239
262
|
}
|
|
@@ -462,8 +485,8 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
462
485
|
addEdge(edges, 'contains', fileNodeId, id, true, {}, definition.location);
|
|
463
486
|
}
|
|
464
487
|
}
|
|
465
|
-
else if (assignment.key.value === 'bitmapfont' ||
|
|
466
|
-
assignment.key.value === 'bitmapfont_override') {
|
|
488
|
+
else if (assignment.key.value.toLowerCase() === 'bitmapfont' ||
|
|
489
|
+
assignment.key.value.toLowerCase() === 'bitmapfont_override') {
|
|
467
490
|
const name = firstScalarInsensitive(child, 'name');
|
|
468
491
|
if (name !== undefined) {
|
|
469
492
|
const assetPaths = [firstScalarInsensitive(child, 'path')]
|
|
@@ -474,10 +497,10 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
474
497
|
const languages = childBlocks(child, 'languages').flatMap((languageBlock) => languageBlock.entries
|
|
475
498
|
.filter((entry) => entry.type === 'scalar')
|
|
476
499
|
.map((entry) => entry.value));
|
|
477
|
-
const colour =
|
|
478
|
-
const borderColour =
|
|
500
|
+
const colour = colourFrom(child, 'color', 'colour');
|
|
501
|
+
const borderColour = colourFrom(child, 'border_color', 'borderColor', 'border_colour');
|
|
479
502
|
const textColours = textColoursFrom(child);
|
|
480
|
-
const override = assignment.key.value === 'bitmapfont_override';
|
|
503
|
+
const override = assignment.key.value.toLowerCase() === 'bitmapfont_override';
|
|
481
504
|
const id = deterministicId('gui_font', {
|
|
482
505
|
path: file.displayPath,
|
|
483
506
|
name,
|
|
@@ -519,7 +542,7 @@ function indexSpritesAndFonts(document, file, fileNodeId, nodes, edges, sprites,
|
|
|
519
542
|
addEdge(edges, 'contains', fileNodeId, id, true, {}, font.location);
|
|
520
543
|
}
|
|
521
544
|
}
|
|
522
|
-
if (assignment.key.value === 'bitmapfonts') {
|
|
545
|
+
if (assignment.key.value.toLowerCase() === 'bitmapfonts') {
|
|
523
546
|
Object.assign(globalTextColours, textColoursFrom(child));
|
|
524
547
|
}
|
|
525
548
|
walk(child);
|
|
@@ -735,11 +735,12 @@ export class ProbabilityAnalyzer {
|
|
|
735
735
|
}
|
|
736
736
|
async store(result, outputs, includeHtml = false, signal, principal) {
|
|
737
737
|
const cached = this.state.byCacheKey.get(result.metadata.cacheKey);
|
|
738
|
+
const workspace = this.engine.resolver.get(result.metadata.workspaceId, principal);
|
|
738
739
|
if (cached?.operation === result.operation &&
|
|
739
740
|
cached.status === result.status &&
|
|
740
|
-
visualOutputs(outputs).length === 0
|
|
741
|
+
visualOutputs(outputs).length === 0 &&
|
|
742
|
+
(await this.engine.artifacts.available(workspace, cached.resources, signal)))
|
|
741
743
|
return cached;
|
|
742
|
-
const workspace = this.engine.resolver.get(result.metadata.workspaceId, principal);
|
|
743
744
|
const provenance = {
|
|
744
745
|
kind: 'probability-analysis',
|
|
745
746
|
toolVersion: PACKAGE_VERSION,
|
|
@@ -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.8';
|
|
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
|
@@ -41,7 +41,7 @@ Scripted-GUI composition follows `parent_scripted_gui` chains even when the link
|
|
|
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
|
|
|
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.
|
|
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. Generated numeric values favour ordinary mid-range states, obey literal limits such as `[?capacity]/10`, keep displayed current/limit pairs coherent, and remain inside progress-bar ranges. 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
45
|
|
|
46
46
|
```json
|
|
47
47
|
{
|
|
@@ -95,4 +95,4 @@ The renderer does not run the game engine. Each render includes a fidelity repor
|
|
|
95
95
|
|
|
96
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.
|
|
97
97
|
|
|
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.
|
|
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. Font selection follows mod/game load order and preview language, accepts Paradox hex and numeric color declarations, and uses the supplied HOI4 bitmap or outline font assets without requiring host font installation. 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.8",
|
|
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.8/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.8/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.8/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.8/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.8/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.8/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.8/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.8/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.8/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.8/schemas/map-operation.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"oneOf": [
|
|
5
5
|
{
|
|
@@ -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.8/schemas/operation-result.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.8/schemas/probability-analysis-result.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.8/schemas/probability-compare-input.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.8/schemas/probability-evaluate-input.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.8/schemas/probability-inspect-input.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.8/schemas/probability-render-input.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.8/schemas/probability-scenario-set.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.8/schemas/probability-sequence-input.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.8/schemas/probability-simulate-input.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.8/schemas/probability-sweep-input.schema.json",
|
|
3
3
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
4
4
|
"type": "object",
|
|
5
5
|
"properties": {
|
package/server.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "io.github.klimPaskov/hoi4-agent-tools",
|
|
4
4
|
"title": "HOI4 Agent Tools",
|
|
5
5
|
"description": "Analyze HOI4 events, tech trees, AI weights, and MTTH; build focus trees, GUIs, and maps.",
|
|
6
|
-
"version": "2.8.
|
|
6
|
+
"version": "2.8.8",
|
|
7
7
|
"repository": {
|
|
8
8
|
"url": "https://github.com/klimPaskov/hoi4-agent-tools",
|
|
9
9
|
"source": "github"
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
{
|
|
14
14
|
"registryType": "npm",
|
|
15
15
|
"identifier": "hoi4-agent-tools",
|
|
16
|
-
"version": "2.8.
|
|
16
|
+
"version": "2.8.8",
|
|
17
17
|
"transport": {
|
|
18
18
|
"type": "stdio"
|
|
19
19
|
}
|