hoi4-agent-tools 3.3.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/dist/hoi4_agent_tools/core/condition-evaluator.js +13 -8
- package/dist/hoi4_agent_tools/core/helper-expansion.js +3 -0
- package/dist/hoi4_agent_tools/event/comparison-selection.js +78 -0
- package/dist/hoi4_agent_tools/event/helper-expansion.js +48 -0
- package/dist/hoi4_agent_tools/event/operations.js +16 -0
- package/dist/hoi4_agent_tools/event/service.js +21 -2
- package/dist/hoi4_agent_tools/focus/compact.js +241 -27
- package/dist/hoi4_agent_tools/focus/layout.js +119 -8
- package/dist/hoi4_agent_tools/focus/render.js +62 -0
- package/dist/hoi4_agent_tools/focus/scenario.js +99 -0
- package/dist/hoi4_agent_tools/focus/tool-operations.js +30 -4
- package/dist/hoi4_agent_tools/gui/assets.js +54 -4
- package/dist/hoi4_agent_tools/gui/layout.js +935 -104
- package/dist/hoi4_agent_tools/gui/renderer.js +47 -17
- package/dist/hoi4_agent_tools/gui/shader.js +515 -0
- package/dist/hoi4_agent_tools/gui/source-graph.js +166 -6
- package/dist/hoi4_agent_tools/gui/studio.js +245 -19
- package/dist/hoi4_agent_tools/gui/tool-operations.js +9 -0
- package/dist/hoi4_agent_tools/gui/validators.js +38 -12
- package/dist/hoi4_agent_tools/map/operations.js +70 -1
- package/dist/hoi4_agent_tools/map/render.js +70 -1
- package/dist/hoi4_agent_tools/map/service.js +30 -6
- package/dist/hoi4_agent_tools/map/tool-operations.js +67 -1
- package/dist/hoi4_agent_tools/mcp/tools/event.js +10 -1
- package/dist/hoi4_agent_tools/mcp/tools/focus.js +14 -4
- package/dist/hoi4_agent_tools/mcp/tools/gui.js +5 -1
- package/dist/hoi4_agent_tools/mcp/tools/map.js +12 -2
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +5 -0
- package/dist/hoi4_agent_tools/probability/evaluation.js +3 -0
- package/dist/hoi4_agent_tools/probability/operations.js +8 -0
- package/dist/hoi4_agent_tools/probability/service.js +33 -12
- package/dist/hoi4_agent_tools/probability/source-analysis.js +1 -0
- package/dist/hoi4_agent_tools/schemas/event.js +8 -0
- package/dist/hoi4_agent_tools/schemas/focus-requests.js +40 -1
- package/dist/hoi4_agent_tools/schemas/gui-requests.js +8 -0
- package/dist/hoi4_agent_tools/schemas/map-requests.js +22 -1
- package/dist/hoi4_agent_tools/schemas/technology.js +7 -0
- package/dist/hoi4_agent_tools/technology/graph.js +25 -10
- package/dist/hoi4_agent_tools/technology/render.js +286 -31
- package/dist/hoi4_agent_tools/technology/service.js +55 -8
- package/dist/hoi4_agent_tools/technology/source-analysis.js +166 -3
- package/dist/hoi4_agent_tools/version.js +1 -1
- package/docs/events.md +3 -1
- package/docs/focus.md +10 -0
- package/docs/gui.md +49 -3
- package/docs/map.md +8 -0
- package/docs/probability.md +3 -1
- package/docs/technology.md +7 -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/decision-inspect-input.schema.json +1 -1
- package/schemas/event-feature-manifest.schema.json +1 -1
- package/schemas/focus-plan.schema.json +1 -1
- package/schemas/focus-planning-sidecar.schema.json +1 -1
- package/schemas/gui-animation-source.schema.json +1 -1
- package/schemas/gui-helper.schema.json +1 -1
- package/schemas/gui-scenario.schema.json +1 -1
- package/schemas/helper-expansion-request.schema.json +1 -1
- package/schemas/helper-expansion-summary.schema.json +1 -1
- package/schemas/impact-inspect-input.schema.json +1 -1
- package/schemas/map-operation.schema.json +1 -1
- package/schemas/mechanic-test-input.schema.json +1 -1
- package/schemas/operation-result.schema.json +1 -1
- package/schemas/package-check-input.schema.json +1 -1
- package/schemas/package-manifest.schema.json +1 -1
- package/schemas/probability-analysis-result.schema.json +1 -1
- package/schemas/probability-compare-input.schema.json +1 -1
- package/schemas/probability-evaluate-input.schema.json +1 -1
- package/schemas/probability-inspect-input.schema.json +1 -1
- package/schemas/probability-render-input.schema.json +1 -1
- package/schemas/probability-scenario-set.schema.json +1 -1
- package/schemas/probability-sequence-input.schema.json +1 -1
- package/schemas/probability-simulate-input.schema.json +1 -1
- package/schemas/probability-sweep-input.schema.json +1 -1
- package/schemas/scenario-suite.schema.json +1 -1
- package/schemas/scenario-test-input.schema.json +1 -1
- package/schemas/shared-scenario.schema.json +1 -1
- package/server.json +2 -2
|
@@ -357,7 +357,50 @@ export async function renderMap(index, options = {}) {
|
|
|
357
357
|
throw new ServiceError('MAP_RENDER_SCALE_INVALID', 'Map render scale must be an integer from 1 through 16');
|
|
358
358
|
const budget = options.budget ?? new RenderBudget();
|
|
359
359
|
assertRenderDimensions(raster.width, raster.height, 'map RGBA source plane');
|
|
360
|
-
|
|
360
|
+
let tile = options.tile;
|
|
361
|
+
if (options.area !== undefined) {
|
|
362
|
+
if (tile !== undefined)
|
|
363
|
+
throw new ServiceError('MAP_RENDER_AREA_CONFLICT', 'Choose a map tile or entity area');
|
|
364
|
+
const ids = new Set(options.area.provinceIds ?? []);
|
|
365
|
+
for (const stateId of options.area.stateIds ?? []) {
|
|
366
|
+
const state = index.statesById.get(stateId);
|
|
367
|
+
if (state === undefined)
|
|
368
|
+
throw new ServiceError('MAP_AREA_STATE_MISSING', 'Map area state ID is missing', {
|
|
369
|
+
stateId,
|
|
370
|
+
});
|
|
371
|
+
for (const id of state.provinces)
|
|
372
|
+
ids.add(id);
|
|
373
|
+
}
|
|
374
|
+
for (const regionId of options.area.regionIds ?? []) {
|
|
375
|
+
const region = index.regionsById.get(regionId);
|
|
376
|
+
if (region === undefined)
|
|
377
|
+
throw new ServiceError('MAP_AREA_REGION_MISSING', 'Map area region ID is missing', {
|
|
378
|
+
regionId,
|
|
379
|
+
});
|
|
380
|
+
for (const id of region.provinces)
|
|
381
|
+
ids.add(id);
|
|
382
|
+
}
|
|
383
|
+
if (ids.size === 0)
|
|
384
|
+
throw new ServiceError('MAP_AREA_EMPTY', 'Map area has no provinces');
|
|
385
|
+
const geometry = [...ids].map((id) => {
|
|
386
|
+
const province = raster.geometry.get(id);
|
|
387
|
+
if (province === undefined)
|
|
388
|
+
throw new ServiceError('MAP_AREA_PROVINCE_MISSING', 'Map area province ID has no raster geometry', { provinceId: id });
|
|
389
|
+
return province;
|
|
390
|
+
});
|
|
391
|
+
const padding = options.area.padding ?? 32;
|
|
392
|
+
const x = Math.max(0, Math.min(...geometry.map(({ minX }) => minX)) - padding);
|
|
393
|
+
const y = Math.max(0, Math.min(...geometry.map(({ minY }) => minY)) - padding);
|
|
394
|
+
const right = Math.min(raster.width, Math.max(...geometry.map(({ maxX }) => maxX)) + padding + 1);
|
|
395
|
+
const bottom = Math.min(raster.height, Math.max(...geometry.map(({ maxY }) => maxY)) + padding + 1);
|
|
396
|
+
tile = { x, y, width: right - x, height: bottom - y };
|
|
397
|
+
if (tile.width > 2_048 || tile.height > 2_048)
|
|
398
|
+
throw new ServiceError('MAP_AREA_TOO_LARGE', 'Map area exceeds the tile limit; select fewer entities', { tile });
|
|
399
|
+
}
|
|
400
|
+
if (tile !== undefined &&
|
|
401
|
+
(tile.x + tile.width > raster.width || tile.y + tile.height > raster.height))
|
|
402
|
+
throw new ServiceError('MAP_TILE_OUT_OF_BOUNDS', 'Map tile extends beyond the province raster', { tile, rasterWidth: raster.width, rasterHeight: raster.height });
|
|
403
|
+
const outputDimensions = budget.reserve((tile?.width ?? raster.width) * resolved.scale, (tile?.height ?? raster.height) * resolved.scale, 'map PNG output');
|
|
361
404
|
const raw = Buffer.alloc(raster.width * raster.height * 4);
|
|
362
405
|
for (let offset = 0; offset < raster.provinceIds.length; offset += 1) {
|
|
363
406
|
if (offset % Math.max(raster.width * 32, 1) === 0)
|
|
@@ -371,6 +414,32 @@ export async function renderMap(index, options = {}) {
|
|
|
371
414
|
}
|
|
372
415
|
await applyOverlays(index, raw, new Set(resolved.overlays), options.signal);
|
|
373
416
|
options.signal?.throwIfAborted();
|
|
417
|
+
if (tile !== undefined) {
|
|
418
|
+
const tileRaw = Buffer.alloc(tile.width * tile.height * 4);
|
|
419
|
+
const provinceIds = new Set();
|
|
420
|
+
for (let row = 0; row < tile.height; row += 1) {
|
|
421
|
+
if (row % 32 === 0)
|
|
422
|
+
await renderCheckpoint(options.signal);
|
|
423
|
+
raw.copy(tileRaw, row * tile.width * 4, ((tile.y + row) * raster.width + tile.x) * 4, ((tile.y + row) * raster.width + tile.x + tile.width) * 4);
|
|
424
|
+
for (let column = 0; column < tile.width; column += 1) {
|
|
425
|
+
const id = raster.provinceIds[(tile.y + row) * raster.width + tile.x + column];
|
|
426
|
+
if (id !== undefined && id >= 0)
|
|
427
|
+
provinceIds.add(id);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const png = await encodePng(tileRaw, tile.width, tile.height, resolved.scale);
|
|
431
|
+
const json = `${canonicalJson({ schemaVersion: 'map-tile.v1', layer: resolved.layer, overlays: resolved.overlays, scale: resolved.scale, tile, raster: { width: raster.width, height: raster.height }, provinceIds: [...provinceIds].sort((left, right) => left - right) })}\n`;
|
|
432
|
+
const html = diffHtmlDocument(`HOI4 map tile - ${resolved.layer}`, png, json);
|
|
433
|
+
return {
|
|
434
|
+
width: outputDimensions.width,
|
|
435
|
+
height: outputDimensions.height,
|
|
436
|
+
tile,
|
|
437
|
+
png,
|
|
438
|
+
json,
|
|
439
|
+
html,
|
|
440
|
+
hashes: { png: sha256Bytes(png), json: sha256Bytes(json), html: sha256Bytes(html) },
|
|
441
|
+
};
|
|
442
|
+
}
|
|
374
443
|
const png = await encodePng(raw, raster.width, raster.height, resolved.scale);
|
|
375
444
|
options.signal?.throwIfAborted();
|
|
376
445
|
const json = `${canonicalJson(await renderMetadata(index, resolved, options.signal))}\n`;
|
|
@@ -481,7 +481,7 @@ export class AgentNudger {
|
|
|
481
481
|
this.scanner = this.engine.scanner;
|
|
482
482
|
this.transactions = this.engine.transactions;
|
|
483
483
|
}
|
|
484
|
-
async scan(workspaceId, principal, signal) {
|
|
484
|
+
async scan(workspaceId, principal, signal, includeExternalScriptReferences = false) {
|
|
485
485
|
signal?.throwIfAborted();
|
|
486
486
|
const workspace = this.resolver.get(workspaceId, principal);
|
|
487
487
|
const sourceRoots = {
|
|
@@ -493,8 +493,27 @@ export class AgentNudger {
|
|
|
493
493
|
const bitmapPatterns = provinceBitmapPatterns(selectorSnapshot.files, sourceRoots.map);
|
|
494
494
|
const contentSnapshot = await this.engine.scan(workspaceId, { patterns: [...sourceTextPatterns(sourceRoots), ...bitmapPatterns] }, principal, signal);
|
|
495
495
|
const locatorSnapshot = await this.engine.scan(workspaceId, { patterns: ['**/*.asset'], rootKinds: ['mod', 'fixture'] }, principal, signal);
|
|
496
|
+
const externalScriptSnapshot = includeExternalScriptReferences
|
|
497
|
+
? await this.engine.scan(workspaceId, {
|
|
498
|
+
patterns: [
|
|
499
|
+
'events/**/*.txt',
|
|
500
|
+
'common/national_focus/**/*.txt',
|
|
501
|
+
'common/decisions/**/*.txt',
|
|
502
|
+
'common/scripted_effects/**/*.txt',
|
|
503
|
+
'common/scripted_triggers/**/*.txt',
|
|
504
|
+
'common/on_actions/**/*.txt',
|
|
505
|
+
'common/ideas/**/*.txt',
|
|
506
|
+
'common/characters/**/*.txt',
|
|
507
|
+
'history/countries/**/*.txt',
|
|
508
|
+
],
|
|
509
|
+
}, principal, signal)
|
|
510
|
+
: undefined;
|
|
496
511
|
signal?.throwIfAborted();
|
|
497
|
-
const files = [
|
|
512
|
+
const files = [
|
|
513
|
+
...contentSnapshot.files,
|
|
514
|
+
...locatorSnapshot.files,
|
|
515
|
+
...(externalScriptSnapshot?.files ?? []),
|
|
516
|
+
].sort((left, right) => left.loadOrder - right.loadOrder || compareCodeUnits(left.displayPath, right.displayPath));
|
|
498
517
|
const revision = hashCanonical(files.map(({ displayPath, loadOrder, sha256 }) => ({ displayPath, loadOrder, sha256 })));
|
|
499
518
|
return {
|
|
500
519
|
workspaceId,
|
|
@@ -512,7 +531,7 @@ export class AgentNudger {
|
|
|
512
531
|
}
|
|
513
532
|
async prepareOperations(workspaceId, operations, principal, signal) {
|
|
514
533
|
signal?.throwIfAborted();
|
|
515
|
-
const snapshot = await this.scan(workspaceId, principal, signal);
|
|
534
|
+
const snapshot = await this.scan(workspaceId, principal, signal, operations.some(({ kind }) => kind === 'renumber_map_entity'));
|
|
516
535
|
const plan = await planMapOperationsAsync(snapshot.index, operations, signal);
|
|
517
536
|
const baselineValidation = await validateMapAsync(snapshot.index, signal === undefined ? {} : { signal });
|
|
518
537
|
const rawValidation = await validateMapAsync(plan.finalIndex, {
|
|
@@ -614,6 +633,9 @@ export class AgentNudger {
|
|
|
614
633
|
const bundle = mapBundleWithSourceHashes(rawBundle, completeSourceHashes);
|
|
615
634
|
const workspace = this.resolver.get(workspaceId, options.principal);
|
|
616
635
|
const layer = options.layer ?? 'province';
|
|
636
|
+
const tileSuffix = options.tile === undefined && options.area === undefined
|
|
637
|
+
? ''
|
|
638
|
+
: `-tile-${hashCanonical({ tile: bundle.tile, area: options.area, overlays: [...new Set(options.overlays ?? [])].sort(), scale: options.scale ?? 1 }).slice(0, 12)}`;
|
|
617
639
|
const provenance = {
|
|
618
640
|
kind: 'map-render',
|
|
619
641
|
toolVersion: PACKAGE_VERSION,
|
|
@@ -624,24 +646,26 @@ export class AgentNudger {
|
|
|
624
646
|
layer,
|
|
625
647
|
overlays: [...new Set(options.overlays ?? [])].sort(),
|
|
626
648
|
scale: options.scale ?? 1,
|
|
649
|
+
...(bundle.tile === undefined ? {} : { tile: bundle.tile }),
|
|
650
|
+
...(options.area === undefined ? {} : { area: options.area }),
|
|
627
651
|
},
|
|
628
652
|
metadata: { sourceHashInventory: sourceEvidence.inventory },
|
|
629
653
|
};
|
|
630
654
|
const writes = [
|
|
631
655
|
{
|
|
632
|
-
name: `map-${layer}.png`,
|
|
656
|
+
name: `map-${layer}${tileSuffix}.png`,
|
|
633
657
|
mimeType: 'image/png',
|
|
634
658
|
content: bundle.png,
|
|
635
659
|
provenance,
|
|
636
660
|
},
|
|
637
661
|
{
|
|
638
|
-
name: `map-${layer}.json`,
|
|
662
|
+
name: `map-${layer}${tileSuffix}.json`,
|
|
639
663
|
mimeType: 'application/json',
|
|
640
664
|
content: bundle.json,
|
|
641
665
|
provenance,
|
|
642
666
|
},
|
|
643
667
|
{
|
|
644
|
-
name: `map-${layer}.html`,
|
|
668
|
+
name: `map-${layer}${tileSuffix}.html`,
|
|
645
669
|
mimeType: 'text/html',
|
|
646
670
|
content: bundle.html,
|
|
647
671
|
provenance,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { boundedSourceHashEvidence, publicArtifactLink, } from '../core/artifacts.js';
|
|
2
2
|
import { canonicalJson, hashCanonical } from '../core/canonical.js';
|
|
3
3
|
import { autonomousResultArtifacts, } from '../core/transaction-execution.js';
|
|
4
|
-
import { emptyServiceResult } from '../core/result.js';
|
|
4
|
+
import { emptyServiceResult, ServiceError } from '../core/result.js';
|
|
5
5
|
import { setInlineFilesScanned } from '../core/operation-result.js';
|
|
6
6
|
import { PACKAGE_VERSION } from '../version.js';
|
|
7
7
|
import { allocateMapIdentifiersAsync, } from './operations.js';
|
|
@@ -299,6 +299,69 @@ export function completeMapRewrite(workspaceId, execution, recipe) {
|
|
|
299
299
|
}
|
|
300
300
|
export async function inspectMap(engine, nudger, input, context) {
|
|
301
301
|
await context.progress(0, 3, 'Inspecting and validating the map');
|
|
302
|
+
if (input.lookupOnly) {
|
|
303
|
+
if (input.allocationRequests.length > 0)
|
|
304
|
+
throw new ServiceError('MAP_LOOKUP_ALLOCATION_UNSUPPORTED', 'Use full map inspection for identifier allocation previews');
|
|
305
|
+
const snapshot = await nudger.scan(context.workspaceId, context.principal, context.signal);
|
|
306
|
+
const catalog = input.query === undefined ? undefined : buildMapCatalog(snapshot.index);
|
|
307
|
+
const queryMatches = catalog === undefined ? [] : searchMapCatalog(catalog, input.query, input.queryLimit);
|
|
308
|
+
const coordinateMatches = input.coordinates.map((coordinate) => lookupMapCoordinate(snapshot.index, coordinate));
|
|
309
|
+
const selected = selectedMapEntities(snapshot, input.provinceIds, input.stateIds, input.regionIds);
|
|
310
|
+
const workspace = engine.resolver.get(context.workspaceId, context.principal);
|
|
311
|
+
const sourceEvidence = mapArtifactSourceEvidence(snapshot.files);
|
|
312
|
+
const selectorHash = hashCanonical({
|
|
313
|
+
provinceIds: input.provinceIds,
|
|
314
|
+
stateIds: input.stateIds,
|
|
315
|
+
regionIds: input.regionIds,
|
|
316
|
+
query: input.query,
|
|
317
|
+
coordinates: input.coordinates,
|
|
318
|
+
}).slice(0, 16);
|
|
319
|
+
const artifact = await engine.artifacts.putChunked(workspace, `map-lookup.${snapshot.revision.slice(0, 16)}.${selectorHash}.json`, 'application/json', `${canonicalJson({ schemaVersion: 1, revision: snapshot.revision, lookupOnly: true, queryMatches, coordinateMatches, selected, sourceHashes: sourceEvidence.complete })}\n`, {
|
|
320
|
+
kind: 'map-lookup',
|
|
321
|
+
toolVersion: PACKAGE_VERSION,
|
|
322
|
+
schemaVersion: 'map-lookup.v1',
|
|
323
|
+
sourceHashes: sourceEvidence.bounded.sourceHashes,
|
|
324
|
+
metadata: { sourceHashInventory: sourceEvidence.bounded.inventory },
|
|
325
|
+
}, 'Bounded map coordinate, catalog, and entity lookup without full validation or overview rendering', context.signal);
|
|
326
|
+
const result = emptyServiceResult(context.workspaceId, {
|
|
327
|
+
revision: snapshot.revision,
|
|
328
|
+
sharedRevision: snapshot.revision,
|
|
329
|
+
width: snapshot.index.raster?.width ?? null,
|
|
330
|
+
height: snapshot.index.raster?.height ?? null,
|
|
331
|
+
definitions: snapshot.index.definitions.length,
|
|
332
|
+
states: snapshot.index.states.length,
|
|
333
|
+
regions: snapshot.index.regions.length,
|
|
334
|
+
ports: snapshot.index.ports.length,
|
|
335
|
+
inspectedProvinceCount: selected.provinces.length,
|
|
336
|
+
inspectedStateCount: selected.states.length,
|
|
337
|
+
inspectedRegionCount: selected.regions.length,
|
|
338
|
+
allocationCount: 0,
|
|
339
|
+
queryMatchCount: queryMatches.length,
|
|
340
|
+
coordinateMatchCount: coordinateMatches.length,
|
|
341
|
+
overviewRendered: false,
|
|
342
|
+
provinceGeometryCount: 0,
|
|
343
|
+
provinceGeometryPixelCount: 0,
|
|
344
|
+
provinceGeometryRowRunCount: 0,
|
|
345
|
+
unknownProvinceIds: [],
|
|
346
|
+
missingGeometryProvinceIds: [],
|
|
347
|
+
lookupOnly: true,
|
|
348
|
+
});
|
|
349
|
+
result.code = 'MAP_LOOKUP_COMPLETED';
|
|
350
|
+
result.validation = {
|
|
351
|
+
passed: true,
|
|
352
|
+
checks: [
|
|
353
|
+
{
|
|
354
|
+
id: 'lookup-only',
|
|
355
|
+
passed: true,
|
|
356
|
+
message: 'Lookup completed; full map validation was not requested',
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
};
|
|
360
|
+
setInlineFilesScanned(result, snapshot.files.map(({ displayPath }) => displayPath));
|
|
361
|
+
result.artifacts = [publicArtifactLink(artifact)];
|
|
362
|
+
await context.progress(3, 3, 'Map lookup complete');
|
|
363
|
+
return result;
|
|
364
|
+
}
|
|
302
365
|
const { snapshot, validation } = await nudger.validate(context.workspaceId, context.principal, context.signal);
|
|
303
366
|
const sharedRevision = snapshot.revision;
|
|
304
367
|
const catalog = buildMapCatalog(snapshot.index);
|
|
@@ -474,6 +537,8 @@ export async function renderMapView(nudger, input, context) {
|
|
|
474
537
|
...(input.layer === undefined ? {} : { layer: input.layer }),
|
|
475
538
|
...(input.overlays === undefined ? {} : { overlays: input.overlays }),
|
|
476
539
|
...(input.scale === undefined ? {} : { scale: input.scale }),
|
|
540
|
+
...(input.tile === undefined ? {} : { tile: input.tile }),
|
|
541
|
+
...(input.area === undefined ? {} : { area: input.area }),
|
|
477
542
|
...(context.principal === undefined ? {} : { principal: context.principal }),
|
|
478
543
|
...(context.signal === undefined ? {} : { signal: context.signal }),
|
|
479
544
|
});
|
|
@@ -483,6 +548,7 @@ export async function renderMapView(nudger, input, context) {
|
|
|
483
548
|
height: rendered.bundle.height,
|
|
484
549
|
hashes: rendered.bundle.hashes,
|
|
485
550
|
offlineRepresentation: true,
|
|
551
|
+
...(rendered.bundle.tile === undefined ? {} : { tile: rendered.bundle.tile }),
|
|
486
552
|
});
|
|
487
553
|
result.code = 'MAP_RENDERED';
|
|
488
554
|
setInlineFilesScanned(result, rendered.filesScanned);
|
|
@@ -31,6 +31,7 @@ const eventRenderInputSchema = z
|
|
|
31
31
|
const eventCompareInputSchema = z
|
|
32
32
|
.object({
|
|
33
33
|
...eventCompareRequestSchema.shape,
|
|
34
|
+
selector: nestedSelectorSchema.optional(),
|
|
34
35
|
before: nestedGraphReferenceSchema.optional(),
|
|
35
36
|
after: nestedGraphReferenceSchema.optional(),
|
|
36
37
|
proposedSources: z.array(nestedProposedSourceSchema).min(1).max(64).optional(),
|
|
@@ -119,6 +120,11 @@ const eventCompareOutputSchema = strictOperationResultSchema(z
|
|
|
119
120
|
counts: z
|
|
120
121
|
.object({
|
|
121
122
|
changes: nonNegativeIntegerSchema,
|
|
123
|
+
selectedChanges: nonNegativeIntegerSchema.optional(),
|
|
124
|
+
omittedChanges: nonNegativeIntegerSchema.optional(),
|
|
125
|
+
unattributedChanges: nonNegativeIntegerSchema.optional(),
|
|
126
|
+
selectedBeforeNodes: nonNegativeIntegerSchema.optional(),
|
|
127
|
+
selectedAfterNodes: nonNegativeIntegerSchema.optional(),
|
|
122
128
|
addedNodes: nonNegativeIntegerSchema,
|
|
123
129
|
removedNodes: nonNegativeIntegerSchema,
|
|
124
130
|
changedNodes: nonNegativeIntegerSchema,
|
|
@@ -146,6 +152,9 @@ const eventCompareOutputSchema = strictOperationResultSchema(z
|
|
|
146
152
|
boundary: z
|
|
147
153
|
.object({
|
|
148
154
|
proposedSources: nonNegativeIntegerSchema,
|
|
155
|
+
selector: eventSelectorSchema.optional(),
|
|
156
|
+
maxChainNodes: nonNegativeIntegerSchema.optional(),
|
|
157
|
+
selectionTruncated: z.boolean().optional(),
|
|
149
158
|
render: z.boolean(),
|
|
150
159
|
maxRenderNodes: nonNegativeIntegerSchema,
|
|
151
160
|
refresh: z.boolean(),
|
|
@@ -179,7 +188,7 @@ export const eventTaskTools = [
|
|
|
179
188
|
{
|
|
180
189
|
name: 'hoi4.event_compare',
|
|
181
190
|
title: 'Compare event chains',
|
|
182
|
-
description: 'Compare cached, artifact-backed, current, or in-memory proposed event graphs
|
|
191
|
+
description: 'Compare cached, artifact-backed, current, or in-memory proposed event graphs, optionally selecting one downstream chain. Full changes and explicit selection coverage are resources.',
|
|
183
192
|
inputSchema: eventCompareInputSchema,
|
|
184
193
|
outputSchema: eventCompareOutputSchema,
|
|
185
194
|
annotations: readOnlyEventTool,
|
|
@@ -30,6 +30,16 @@ const focusInspectOutputSchema = strictOperationResultSchema(z
|
|
|
30
30
|
layoutDecisionCount: nonNegativeIntegerSchema,
|
|
31
31
|
layoutMetrics: focusLayoutMetricsSchema,
|
|
32
32
|
diagnosticCount: nonNegativeIntegerSchema,
|
|
33
|
+
scenarioSummary: z
|
|
34
|
+
.object({
|
|
35
|
+
completed: nonNegativeIntegerSchema,
|
|
36
|
+
blocked: nonNegativeIntegerSchema,
|
|
37
|
+
unresolved: nonNegativeIntegerSchema,
|
|
38
|
+
structuralCandidates: nonNegativeIntegerSchema,
|
|
39
|
+
choiceConflictCount: nonNegativeIntegerSchema,
|
|
40
|
+
})
|
|
41
|
+
.strict()
|
|
42
|
+
.optional(),
|
|
33
43
|
})
|
|
34
44
|
.strict())
|
|
35
45
|
.max(100),
|
|
@@ -125,7 +135,7 @@ export const focusTaskTools = [
|
|
|
125
135
|
{
|
|
126
136
|
name: 'hoi4.focus_inspect',
|
|
127
137
|
title: 'Inspect focus trees',
|
|
128
|
-
description: 'Inspect
|
|
138
|
+
description: 'Inspect focus trees and continuous palettes, including source references, diagnostics, and layout.',
|
|
129
139
|
inputSchema: focusInspectRequestSchema,
|
|
130
140
|
outputSchema: focusInspectOutputSchema,
|
|
131
141
|
annotations: artifactProducing,
|
|
@@ -133,7 +143,7 @@ export const focusTaskTools = [
|
|
|
133
143
|
{
|
|
134
144
|
name: 'hoi4.focus_render',
|
|
135
145
|
title: 'Render focus review artifacts',
|
|
136
|
-
description: 'Render
|
|
146
|
+
description: 'Render focus HTML, SVG, JSON, and source maps. Use hoi4.focus_raster for decoded icons and PNG.',
|
|
137
147
|
inputSchema: focusRenderRequestSchema,
|
|
138
148
|
outputSchema: focusRenderOutputSchema,
|
|
139
149
|
annotations: artifactProducing,
|
|
@@ -141,7 +151,7 @@ export const focusTaskTools = [
|
|
|
141
151
|
{
|
|
142
152
|
name: 'hoi4.focus_raster',
|
|
143
153
|
title: 'Rasterize focus review artifacts',
|
|
144
|
-
description: '
|
|
154
|
+
description: 'Render focus PNG with decoded source icons; also returns HTML, SVG, and JSON.',
|
|
145
155
|
inputSchema: focusRenderRequestSchema,
|
|
146
156
|
outputSchema: focusRenderOutputSchema,
|
|
147
157
|
annotations: artifactProducing,
|
|
@@ -149,7 +159,7 @@ export const focusTaskTools = [
|
|
|
149
159
|
{
|
|
150
160
|
name: 'hoi4.focus_rewrite',
|
|
151
161
|
title: 'Create or clean up focus content',
|
|
152
|
-
description: 'Create or
|
|
162
|
+
description: 'Create or compact a focus tree or palette from a complete plan. Use treeId for existing trees and createIfMissing for new files.',
|
|
153
163
|
inputSchema: focusRewriteRequestSchema,
|
|
154
164
|
outputSchema: focusPlanOutputSchema,
|
|
155
165
|
annotations: {
|
|
@@ -43,6 +43,10 @@ const guiRenderOutputSchema = strictOperationResultSchema(z
|
|
|
43
43
|
comparison: z
|
|
44
44
|
.object({ changedPixels: nonNegativeIntegerSchema, changedRatio: z.number().min(0).max(1) })
|
|
45
45
|
.strict(),
|
|
46
|
+
sourceComparison: z
|
|
47
|
+
.object({ changedPixels: nonNegativeIntegerSchema, changedRatio: z.number().min(0).max(1) })
|
|
48
|
+
.strict()
|
|
49
|
+
.optional(),
|
|
46
50
|
fidelityCounts: countRecordSchema,
|
|
47
51
|
offlineRepresentation: z.literal(true),
|
|
48
52
|
})
|
|
@@ -76,7 +80,7 @@ export const guiTaskTools = [
|
|
|
76
80
|
{
|
|
77
81
|
name: 'hoi4.gui_render',
|
|
78
82
|
title: 'Render scripted GUI artifacts',
|
|
79
|
-
description: 'Render GUI states, resolutions,
|
|
83
|
+
description: 'Render GUI states, resolutions, scenarios, hierarchy, comparisons, and diagnostics.',
|
|
80
84
|
inputSchema: guiRenderRequestSchema,
|
|
81
85
|
outputSchema: guiRenderOutputSchema,
|
|
82
86
|
annotations: artifactProducing,
|
|
@@ -75,6 +75,7 @@ const mapWorkspaceInspectOutputSchema = strictOperationResultSchema(z
|
|
|
75
75
|
provinceGeometryCount: nonNegativeIntegerSchema,
|
|
76
76
|
provinceGeometryPixelCount: nonNegativeIntegerSchema,
|
|
77
77
|
provinceGeometryRowRunCount: nonNegativeIntegerSchema,
|
|
78
|
+
lookupOnly: z.boolean().optional(),
|
|
78
79
|
unknownProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
79
80
|
missingGeometryProvinceIds: z.array(nonNegativeIntegerSchema).max(512),
|
|
80
81
|
})
|
|
@@ -86,6 +87,15 @@ const mapRenderOutputSchema = strictOperationResultSchema(z
|
|
|
86
87
|
height: nonNegativeIntegerSchema,
|
|
87
88
|
hashes: bitmapRenderHashesSchema,
|
|
88
89
|
offlineRepresentation: z.literal(true),
|
|
90
|
+
tile: z
|
|
91
|
+
.object({
|
|
92
|
+
x: nonNegativeIntegerSchema,
|
|
93
|
+
y: nonNegativeIntegerSchema,
|
|
94
|
+
width: nonNegativeIntegerSchema,
|
|
95
|
+
height: nonNegativeIntegerSchema,
|
|
96
|
+
})
|
|
97
|
+
.strict()
|
|
98
|
+
.optional(),
|
|
89
99
|
})
|
|
90
100
|
.strict());
|
|
91
101
|
const mapPlanOutputSchema = strictOperationResultSchema(z
|
|
@@ -113,7 +123,7 @@ export const mapTaskTools = [
|
|
|
113
123
|
{
|
|
114
124
|
name: 'hoi4.map_inspect',
|
|
115
125
|
title: 'Inspect HOI4 map',
|
|
116
|
-
description: '
|
|
126
|
+
description: 'Validate and inspect the complete map, or use lookupOnly for bounded coordinate and entity lookups.',
|
|
117
127
|
inputSchema: mapInspectRequestSchema,
|
|
118
128
|
outputSchema: mapWorkspaceInspectOutputSchema,
|
|
119
129
|
annotations: artifactProducing,
|
|
@@ -121,7 +131,7 @@ export const mapTaskTools = [
|
|
|
121
131
|
{
|
|
122
132
|
name: 'hoi4.map_render',
|
|
123
133
|
title: 'Render map inspection artifacts',
|
|
124
|
-
description: 'Render searchable full-map PNG, JSON, and HTML.',
|
|
134
|
+
description: 'Render searchable full-map PNG, JSON, and HTML, or a reusable source-coordinate tile.',
|
|
125
135
|
inputSchema: mapRenderRequestSchema,
|
|
126
136
|
outputSchema: mapRenderOutputSchema,
|
|
127
137
|
annotations: artifactProducing,
|
|
@@ -16,6 +16,9 @@ const analysisDataSchema = z
|
|
|
16
16
|
cacheKey: sha256Schema,
|
|
17
17
|
scenarios: nonNegativeIntegerSchema,
|
|
18
18
|
candidates: nonNegativeIntegerSchema,
|
|
19
|
+
eligibleCandidates: nonNegativeIntegerSchema,
|
|
20
|
+
excludedCandidates: nonNegativeIntegerSchema,
|
|
21
|
+
unresolvedCandidates: nonNegativeIntegerSchema,
|
|
19
22
|
scopePools: nonNegativeIntegerSchema.optional(),
|
|
20
23
|
scopePoolCandidates: nonNegativeIntegerSchema.optional(),
|
|
21
24
|
unresolved: nonNegativeIntegerSchema,
|
|
@@ -61,6 +64,8 @@ const inspectDataSchema = z
|
|
|
61
64
|
candidateExamples: z.array(z.string().max(512)).max(10),
|
|
62
65
|
poolComplete: z.boolean().optional(),
|
|
63
66
|
requiredInputs: nonNegativeIntegerSchema,
|
|
67
|
+
requiredInputPaths: z.array(z.string().max(1024)).max(32),
|
|
68
|
+
requiredInputPathsTruncated: z.boolean(),
|
|
64
69
|
unresolved: nonNegativeIntegerSchema,
|
|
65
70
|
})
|
|
66
71
|
.strict();
|
|
@@ -114,6 +114,9 @@ export function evaluateExactCandidates(surface, scenario, definitions) {
|
|
|
114
114
|
...external.trace,
|
|
115
115
|
],
|
|
116
116
|
unresolved: [...eligibility.unresolved, ...value.unresolved, ...external.unresolved],
|
|
117
|
+
eligibilityUnresolved: eligibility.unresolved,
|
|
118
|
+
valueUnresolved: value.unresolved,
|
|
119
|
+
externalUnresolved: external.unresolved,
|
|
117
120
|
provenance: [
|
|
118
121
|
...candidate.provenance,
|
|
119
122
|
...(eligibility.helperProvenance ?? []),
|
|
@@ -21,6 +21,9 @@ export function probabilityAnalysisData(result) {
|
|
|
21
21
|
cacheKey: result.metadata.cacheKey,
|
|
22
22
|
scenarios: result.scenarios.length,
|
|
23
23
|
candidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.length, 0),
|
|
24
|
+
eligibleCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'true').length, 0),
|
|
25
|
+
excludedCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'false').length, 0),
|
|
26
|
+
unresolvedCandidates: result.scenarios.reduce((sum, scenario) => sum + scenario.candidates.filter(({ eligibility }) => eligibility === 'unresolved').length, 0),
|
|
24
27
|
...(result.scenarios.some(({ scopePools }) => (scopePools?.length ?? 0) > 0)
|
|
25
28
|
? {
|
|
26
29
|
scopePools: result.scenarios.reduce((sum, scenario) => sum + (scenario.scopePools?.length ?? 0), 0),
|
|
@@ -87,6 +90,8 @@ export function normalizeProbabilityRenderRequest(input) {
|
|
|
87
90
|
}
|
|
88
91
|
export async function inspectProbabilities(analyzer, input, context) {
|
|
89
92
|
const inspected = await analyzer.inspect(runtime(context, input.refresh), input.adapter, input.source, input.candidatePool, input.customPoolManifest);
|
|
93
|
+
const sourceRequiredInputPaths = (inspected.surface?.requiredInputs ?? []).filter((path) => path !== 'focus.external_factors_complete' && path !== 'technology.external_factors_complete');
|
|
94
|
+
const adapterRequiredInputPaths = (inspected.surface?.requiredInputs ?? []).filter((path) => path === 'focus.external_factors_complete' || path === 'technology.external_factors_complete');
|
|
90
95
|
const result = emptyServiceResult(context.workspaceId, {
|
|
91
96
|
adapters: inspected.adapters.length,
|
|
92
97
|
...(inspected.surface === undefined
|
|
@@ -120,6 +125,9 @@ export async function inspectProbabilities(analyzer, input, context) {
|
|
|
120
125
|
})) ?? [],
|
|
121
126
|
candidateExamples: inspected.discovery?.exampleCandidateIds ?? [],
|
|
122
127
|
requiredInputs: inspected.surface?.requiredInputs.length ?? 0,
|
|
128
|
+
requiredInputPaths: sourceRequiredInputPaths.slice(0, 32),
|
|
129
|
+
requiredInputPathsTruncated: sourceRequiredInputPaths.length > 32,
|
|
130
|
+
adapterRequiredInputPaths,
|
|
123
131
|
unresolved: inspected.surface?.unsupported.length ?? 0,
|
|
124
132
|
});
|
|
125
133
|
result.code =
|
|
@@ -9,7 +9,7 @@ import { PACKAGE_VERSION } from '../version.js';
|
|
|
9
9
|
import { probabilityAnalysisResultSchema } from '../schemas/probability.js';
|
|
10
10
|
import { probabilityAdapter, probabilityAdapters } from './adapters.js';
|
|
11
11
|
import { compareProbabilityResults } from './compare.js';
|
|
12
|
-
import { evaluateExactCandidates, evaluateSurfaceScenarios } from './evaluation.js';
|
|
12
|
+
import { evaluateExactCandidates, evaluateSurfaceScenarios, } from './evaluation.js';
|
|
13
13
|
import { VERIFIED_GAME_CHECKSUM, VERIFIED_GAME_RAW_VERSION, VERIFIED_GAME_VERSION, } from './model.js';
|
|
14
14
|
import { renderProbabilityResult } from './render.js';
|
|
15
15
|
import { analyzeSequence, evaluateCustomPoolPoint } from './sequence.js';
|
|
@@ -17,6 +17,28 @@ import { simulateSurface } from './simulation.js';
|
|
|
17
17
|
import { Rational } from './rational.js';
|
|
18
18
|
import { discoverWeightedSurface, inventoryWeightedSurfaces, } from './source-analysis.js';
|
|
19
19
|
import { sweepSurface } from './sweep.js';
|
|
20
|
+
function missingScenarioInputs(unresolved) {
|
|
21
|
+
return [
|
|
22
|
+
...new Set(unresolved.flatMap(({ code, path, details }) => {
|
|
23
|
+
if (typeof details?.scenarioInput === 'string')
|
|
24
|
+
return [details.scenarioInput];
|
|
25
|
+
if (code === 'VALUE_UNRESOLVED' && path !== undefined && !/^(?:@|constant:)/u.test(path))
|
|
26
|
+
return [path];
|
|
27
|
+
if (path !== undefined &&
|
|
28
|
+
(code === 'FOCUS_EXTERNAL_FACTORS_UNDECLARED' ||
|
|
29
|
+
code === 'TECHNOLOGY_EXTERNAL_FACTORS_UNDECLARED'))
|
|
30
|
+
return [path];
|
|
31
|
+
return [];
|
|
32
|
+
})),
|
|
33
|
+
].sort(compareCodeUnits);
|
|
34
|
+
}
|
|
35
|
+
function blockingInspectionUnresolved(candidate) {
|
|
36
|
+
if (candidate.eligibility === 'false')
|
|
37
|
+
return [];
|
|
38
|
+
if (candidate.eligibility === 'unresolved')
|
|
39
|
+
return [...candidate.eligibilityUnresolved, ...candidate.externalUnresolved];
|
|
40
|
+
return [...candidate.valueUnresolved, ...candidate.externalUnresolved];
|
|
41
|
+
}
|
|
20
42
|
const states = new WeakMap();
|
|
21
43
|
const ANALYSIS_CACHE_ENTRIES = 16;
|
|
22
44
|
function resolveSourceBackedAdapter(snapshot, source, candidatePool, requested) {
|
|
@@ -1063,9 +1085,7 @@ export class ProbabilityAnalyzer {
|
|
|
1063
1085
|
}
|
|
1064
1086
|
const definitions = ClausewitzEvaluationDefinitions.build(snapshot);
|
|
1065
1087
|
const inspectedCandidates = evaluateExactCandidates(surface, { id: 'inspection', state: {} }, definitions);
|
|
1066
|
-
const requiredInputs =
|
|
1067
|
-
...new Set(inspectedCandidates.flatMap(({ unresolved }) => unresolved.flatMap(({ path }) => (path === undefined ? [] : [path])))),
|
|
1068
|
-
].sort();
|
|
1088
|
+
const requiredInputs = missingScenarioInputs(inspectedCandidates.flatMap(blockingInspectionUnresolved));
|
|
1069
1089
|
const report = {
|
|
1070
1090
|
schemaVersion: 'probability-inspection.v2',
|
|
1071
1091
|
workspaceId: context.workspaceId,
|
|
@@ -1079,9 +1099,8 @@ export class ProbabilityAnalyzer {
|
|
|
1079
1099
|
candidateCount: surface.candidates.length,
|
|
1080
1100
|
candidates: surface.candidates.map((candidate, index) => {
|
|
1081
1101
|
const evaluated = inspectedCandidates[index];
|
|
1082
|
-
const
|
|
1083
|
-
|
|
1084
|
-
].sort();
|
|
1102
|
+
const blockingUnresolved = blockingInspectionUnresolved(evaluated);
|
|
1103
|
+
const candidateRequiredInputs = missingScenarioInputs(blockingUnresolved);
|
|
1085
1104
|
return {
|
|
1086
1105
|
id: candidate.id,
|
|
1087
1106
|
sourceKind: candidate.sourceKind,
|
|
@@ -1092,11 +1111,13 @@ export class ProbabilityAnalyzer {
|
|
|
1092
1111
|
referencedProvenance: evaluated.provenance.filter(({ path, symbol }) => !candidate.provenance.some((source) => source.path === path && source.symbol === symbol)),
|
|
1093
1112
|
analysisSupport: candidateRequiredInputs.length > 0
|
|
1094
1113
|
? 'requires_scenario'
|
|
1095
|
-
:
|
|
1096
|
-
? '
|
|
1097
|
-
: surface.adapter.
|
|
1098
|
-
? '
|
|
1099
|
-
:
|
|
1114
|
+
: blockingUnresolved.length > 0
|
|
1115
|
+
? 'unresolved_source'
|
|
1116
|
+
: surface.adapter.selectionRule === 'score_only'
|
|
1117
|
+
? 'score_only'
|
|
1118
|
+
: surface.adapter.completePoolRequired && !surface.poolComplete
|
|
1119
|
+
? 'requires_complete_pool'
|
|
1120
|
+
: 'exact',
|
|
1100
1121
|
};
|
|
1101
1122
|
}),
|
|
1102
1123
|
requiredInputs,
|
|
@@ -421,6 +421,7 @@ export function inventoryWeightedSurfaces(snapshot, source, candidatePool = [],
|
|
|
421
421
|
? candidates.length
|
|
422
422
|
: candidates.filter(({ id }) => wanted.has(id)).length,
|
|
423
423
|
exampleCandidateIds: [...new Set(candidates.map(({ id }) => id))].slice(0, 10),
|
|
424
|
+
candidateCatalog: candidates.map(({ id, provenance }) => ({ id, provenance })),
|
|
424
425
|
sourcePaths: [
|
|
425
426
|
...new Set(candidates.flatMap(({ provenance }) => provenance.map(({ path }) => path))),
|
|
426
427
|
].sort(compareCodeUnits),
|
|
@@ -151,6 +151,12 @@ export function validateEventCompareRequest(value, context) {
|
|
|
151
151
|
path: ['after'],
|
|
152
152
|
message: 'after and proposedSources are mutually exclusive',
|
|
153
153
|
});
|
|
154
|
+
if (value.maxChainNodes !== undefined && value.selector === undefined)
|
|
155
|
+
context.addIssue({
|
|
156
|
+
code: 'custom',
|
|
157
|
+
path: ['maxChainNodes'],
|
|
158
|
+
message: 'maxChainNodes requires selector',
|
|
159
|
+
});
|
|
154
160
|
}
|
|
155
161
|
export const eventCompareRequestSchema = z
|
|
156
162
|
.object({
|
|
@@ -158,6 +164,8 @@ export const eventCompareRequestSchema = z
|
|
|
158
164
|
before: eventGraphReferenceSchema.optional(),
|
|
159
165
|
after: eventGraphReferenceSchema.optional(),
|
|
160
166
|
proposedSources: z.array(eventProposedSourceSchema).min(1).max(64).optional(),
|
|
167
|
+
selector: eventSelectorSchema.optional(),
|
|
168
|
+
maxChainNodes: z.number().int().min(1).max(5_000).optional(),
|
|
161
169
|
render: z.boolean().optional(),
|
|
162
170
|
maxRenderNodes: z.number().int().min(1).max(240).optional(),
|
|
163
171
|
refresh: z.boolean().optional(),
|