hoi4-agent-tools 3.0.0 → 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 +6 -0
- package/README.md +3 -1
- package/dist/bin/stdio.js +4 -0
- package/dist/hoi4_agent_tools/core/domain-scan-patterns.js +12 -0
- package/dist/hoi4_agent_tools/mcp/tools/chaosx.js +483 -0
- package/dist/hoi4_agent_tools/mcp/tools/probability.js +9 -5
- package/dist/hoi4_agent_tools/probability/service.js +48 -6
- 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/images/readme/event-chain-mcp.png +0 -0
- package/docs/images/readme/probability-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 +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
|
@@ -17,6 +17,43 @@ import { Rational } from './rational.js';
|
|
|
17
17
|
import { discoverWeightedSurface, inventoryWeightedSurfaces, } from './source-analysis.js';
|
|
18
18
|
import { sweepSurface } from './sweep.js';
|
|
19
19
|
const states = new WeakMap();
|
|
20
|
+
function resolveSourceBackedAdapter(snapshot, source, candidatePool, requested) {
|
|
21
|
+
if (requested !== undefined && requested !== 'custom_weighted_pool') {
|
|
22
|
+
try {
|
|
23
|
+
discoverWeightedSurface(snapshot, requested, source, candidatePool);
|
|
24
|
+
return requested;
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
if (!(error instanceof ServiceError) ||
|
|
28
|
+
(error.code !== 'PROBABILITY_SURFACE_EMPTY' &&
|
|
29
|
+
error.code !== 'PROBABILITY_IDENTIFIER_NOT_FOUND'))
|
|
30
|
+
throw error;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const inventory = inventoryWeightedSurfaces(snapshot, source, candidatePool);
|
|
34
|
+
const viable = inventory.adapters
|
|
35
|
+
.filter(({ candidateCount, identifierMatchCount, candidatePoolMatchCount }) => candidateCount > 0 &&
|
|
36
|
+
(source.identifier === undefined || identifierMatchCount > 0) &&
|
|
37
|
+
(candidatePool.length === 0 || candidatePoolMatchCount > 0))
|
|
38
|
+
.sort((left, right) => right.identifierMatchCount - left.identifierMatchCount ||
|
|
39
|
+
right.candidatePoolMatchCount - left.candidatePoolMatchCount ||
|
|
40
|
+
right.candidateCount - left.candidateCount ||
|
|
41
|
+
compareCodeUnits(left.adapterId, right.adapterId));
|
|
42
|
+
if (viable.length === 1)
|
|
43
|
+
return viable[0].adapterId;
|
|
44
|
+
if (viable.length === 0)
|
|
45
|
+
throw new ServiceError('PROBABILITY_SURFACE_EMPTY', 'No weighted blocks matched this request', {
|
|
46
|
+
requestedAdapter: requested,
|
|
47
|
+
source,
|
|
48
|
+
candidatePool,
|
|
49
|
+
availableAdapters: inventory.adapters.filter(({ candidateCount }) => candidateCount > 0),
|
|
50
|
+
});
|
|
51
|
+
throw new ServiceError('PROBABILITY_ADAPTER_AMBIGUOUS', 'Several weighted adapters match this source; select one adapter or narrow the source by identifier or line', {
|
|
52
|
+
requestedAdapter: requested,
|
|
53
|
+
source,
|
|
54
|
+
matchingAdapters: viable,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
20
57
|
function analyzerState(engine) {
|
|
21
58
|
let state = states.get(engine);
|
|
22
59
|
if (state === undefined) {
|
|
@@ -853,7 +890,9 @@ export class ProbabilityAnalyzer {
|
|
|
853
890
|
return { adapters: probabilityAdapters(), artifacts: [], filesScanned: [] };
|
|
854
891
|
if (customPoolManifest !== undefined && source !== undefined)
|
|
855
892
|
throw new ServiceError('PROBABILITY_INPUT_AMBIGUOUS', 'Provide either source or customPoolManifest for inspection');
|
|
856
|
-
if (customPoolManifest !== undefined &&
|
|
893
|
+
if (customPoolManifest !== undefined &&
|
|
894
|
+
adapter !== undefined &&
|
|
895
|
+
adapter !== 'custom_weighted_pool')
|
|
857
896
|
throw new ServiceError('PROBABILITY_ADAPTER_MISMATCH', 'customPoolManifest inspection requires adapter custom_weighted_pool');
|
|
858
897
|
const snapshot = await this.scan(context, [source?.path], source === undefined ? 'none' : source.inlineClausewitz === undefined ? 'domain' : 'shared');
|
|
859
898
|
if (customPoolManifest !== undefined) {
|
|
@@ -1040,15 +1079,18 @@ export class ProbabilityAnalyzer {
|
|
|
1040
1079
|
const usingManifest = request.customPoolManifest !== undefined;
|
|
1041
1080
|
if (usingManifest !== (request.source === undefined))
|
|
1042
1081
|
throw new ServiceError('PROBABILITY_INPUT_AMBIGUOUS', 'Provide exactly one source or customPoolManifest');
|
|
1043
|
-
if (usingManifest &&
|
|
1082
|
+
if (usingManifest &&
|
|
1083
|
+
request.adapter !== undefined &&
|
|
1084
|
+
request.adapter !== 'custom_weighted_pool')
|
|
1044
1085
|
throw new ServiceError('PROBABILITY_ADAPTER_MISMATCH', 'customPoolManifest requires the custom_weighted_pool adapter');
|
|
1045
|
-
if (!usingManifest && request.adapter === 'custom_weighted_pool')
|
|
1046
|
-
throw new ServiceError('PROBABILITY_MANIFEST_REQUIRED', 'custom_weighted_pool requires customPoolManifest');
|
|
1047
|
-
const gameVersionVerification = await this.verifyGameVersion(request, request.adapter);
|
|
1048
1086
|
const snapshot = await this.scan(request, [request.source?.path], usingManifest ? 'none' : request.source?.inlineClausewitz === undefined ? 'domain' : 'shared');
|
|
1087
|
+
const resolvedAdapter = usingManifest
|
|
1088
|
+
? 'custom_weighted_pool'
|
|
1089
|
+
: resolveSourceBackedAdapter(snapshot, request.source, request.candidatePool ?? [], request.adapter);
|
|
1090
|
+
const gameVersionVerification = await this.verifyGameVersion(request, resolvedAdapter);
|
|
1049
1091
|
const surface = usingManifest
|
|
1050
1092
|
? customSurface(request.customPoolManifest, snapshot.revision)
|
|
1051
|
-
: discoverWeightedSurface(snapshot,
|
|
1093
|
+
: discoverWeightedSurface(snapshot, resolvedAdapter, request.source, request.candidatePool ?? []);
|
|
1052
1094
|
const definitions = ClausewitzEvaluationDefinitions.build(snapshot);
|
|
1053
1095
|
const metadata = this.metadata(request, snapshot, surface, request.scenarioSet, usingManifest
|
|
1054
1096
|
? request.customPoolManifest.candidates.map(({ id }) => id)
|
|
@@ -4,12 +4,17 @@ function byId(values) {
|
|
|
4
4
|
}
|
|
5
5
|
function placementSignature(values) {
|
|
6
6
|
return hashCanonical(values
|
|
7
|
-
.map(({ folderId, x, y, xExpression, yExpression }) => ({
|
|
7
|
+
.map(({ folderId, x, y, xExpression, yExpression, layoutSize, layoutWidth, layoutHeight, layoutOffsetX, layoutOffsetY, }) => ({
|
|
8
8
|
folderId,
|
|
9
9
|
x: x ?? null,
|
|
10
10
|
y: y ?? null,
|
|
11
11
|
xExpression: xExpression ?? null,
|
|
12
12
|
yExpression: yExpression ?? null,
|
|
13
|
+
layoutSize: layoutSize ?? null,
|
|
14
|
+
layoutWidth: layoutWidth ?? null,
|
|
15
|
+
layoutHeight: layoutHeight ?? null,
|
|
16
|
+
layoutOffsetX: layoutOffsetX ?? null,
|
|
17
|
+
layoutOffsetY: layoutOffsetY ?? null,
|
|
13
18
|
}))
|
|
14
19
|
.sort((left, right) => compareCodeUnits(left.folderId, right.folderId) ||
|
|
15
20
|
(left.x ?? 0) - (right.x ?? 0) ||
|
|
@@ -155,7 +155,51 @@ function activeGridboxes(values) {
|
|
|
155
155
|
.sort((left, right) => compareCodeUnits(left.folderId ?? '', right.folderId ?? '') ||
|
|
156
156
|
compareCodeUnits(left.name, right.name));
|
|
157
157
|
}
|
|
158
|
-
function
|
|
158
|
+
function activeItemLayouts(values) {
|
|
159
|
+
const groups = new Map();
|
|
160
|
+
for (const layout of values) {
|
|
161
|
+
const key = `${layout.folderId}\0${layout.layoutSize}`;
|
|
162
|
+
const group = groups.get(key) ?? [];
|
|
163
|
+
group.push(layout);
|
|
164
|
+
groups.set(key, group);
|
|
165
|
+
}
|
|
166
|
+
return [...groups.values()]
|
|
167
|
+
.map((group) => group.sort((left, right) => right.loadOrder - left.loadOrder ||
|
|
168
|
+
compareCodeUnits(left.sourcePath, right.sourcePath) ||
|
|
169
|
+
left.location.start.offset - right.location.start.offset)[0])
|
|
170
|
+
.sort((left, right) => compareCodeUnits(left.folderId, right.folderId) ||
|
|
171
|
+
compareCodeUnits(left.layoutSize, right.layoutSize));
|
|
172
|
+
}
|
|
173
|
+
function activeYearMarkers(values) {
|
|
174
|
+
const groups = new Map();
|
|
175
|
+
for (const marker of values) {
|
|
176
|
+
const key = `${marker.folderId}\0${marker.name}`;
|
|
177
|
+
const group = groups.get(key) ?? [];
|
|
178
|
+
group.push(marker);
|
|
179
|
+
groups.set(key, group);
|
|
180
|
+
}
|
|
181
|
+
return [...groups.values()]
|
|
182
|
+
.map((group) => group.sort((left, right) => right.loadOrder - left.loadOrder ||
|
|
183
|
+
compareCodeUnits(left.sourcePath, right.sourcePath) ||
|
|
184
|
+
left.location.start.offset - right.location.start.offset)[0])
|
|
185
|
+
.sort((left, right) => compareCodeUnits(left.folderId, right.folderId) ||
|
|
186
|
+
left.year - right.year ||
|
|
187
|
+
compareCodeUnits(left.name, right.name));
|
|
188
|
+
}
|
|
189
|
+
function enrichPlacements(placements, edges, gridboxes, technologies, unlocks, itemLayouts, signal) {
|
|
190
|
+
const technologyById = new Map(technologies.map((technology) => [technology.id, technology]));
|
|
191
|
+
const unlockKindsByTechnology = new Map();
|
|
192
|
+
for (const unlock of unlocks) {
|
|
193
|
+
const kinds = unlockKindsByTechnology.get(unlock.technologyId) ?? new Set();
|
|
194
|
+
kinds.add(unlock.kind);
|
|
195
|
+
unlockKindsByTechnology.set(unlock.technologyId, kinds);
|
|
196
|
+
}
|
|
197
|
+
const layoutsByFolder = new Map();
|
|
198
|
+
for (const layout of itemLayouts) {
|
|
199
|
+
const layouts = layoutsByFolder.get(layout.folderId) ?? new Map();
|
|
200
|
+
layouts.set(layout.layoutSize, layout);
|
|
201
|
+
layoutsByFolder.set(layout.folderId, layouts);
|
|
202
|
+
}
|
|
159
203
|
const placementsByTechnology = new Map();
|
|
160
204
|
for (const placement of placements) {
|
|
161
205
|
const group = placementsByTechnology.get(placement.technologyId) ?? [];
|
|
@@ -198,6 +242,15 @@ function enrichPlacements(placements, edges, gridboxes, signal) {
|
|
|
198
242
|
: gridboxes.filter(({ name, folderId }) => name === expectedGridbox &&
|
|
199
243
|
(folderId === undefined || folderId === placement.folderId));
|
|
200
244
|
const gridbox = candidates[0];
|
|
245
|
+
const technology = technologyById.get(placement.technologyId);
|
|
246
|
+
const folderLayouts = layoutsByFolder.get(placement.folderId);
|
|
247
|
+
const unlockKinds = unlockKindsByTechnology.get(placement.technologyId) ?? new Set();
|
|
248
|
+
const unlocksEquipment = unlockKinds.has('equipment') || unlockKinds.has('equipment_module');
|
|
249
|
+
const layoutSize = technology?.layoutSize === 'small' ||
|
|
250
|
+
(folderLayouts?.has('small') === true && !unlocksEquipment)
|
|
251
|
+
? 'small'
|
|
252
|
+
: 'large';
|
|
253
|
+
const itemLayout = folderLayouts?.get(layoutSize) ?? folderLayouts?.get('large');
|
|
201
254
|
let pixelX;
|
|
202
255
|
let pixelY;
|
|
203
256
|
if (gridbox !== undefined &&
|
|
@@ -223,6 +276,12 @@ function enrichPlacements(placements, edges, gridboxes, signal) {
|
|
|
223
276
|
...(gridbox === undefined ? {} : { gridboxId: gridbox.id }),
|
|
224
277
|
...(pixelX === undefined ? {} : { pixelX }),
|
|
225
278
|
...(pixelY === undefined ? {} : { pixelY }),
|
|
279
|
+
layoutSize,
|
|
280
|
+
...(itemLayout === undefined ? {} : { itemLayoutId: itemLayout.id }),
|
|
281
|
+
...(itemLayout?.size.width === undefined ? {} : { layoutWidth: itemLayout.size.width }),
|
|
282
|
+
...(itemLayout?.size.height === undefined ? {} : { layoutHeight: itemLayout.size.height }),
|
|
283
|
+
...(itemLayout?.position.x === undefined ? {} : { layoutOffsetX: itemLayout.position.x }),
|
|
284
|
+
...(itemLayout?.position.y === undefined ? {} : { layoutOffsetY: itemLayout.position.y }),
|
|
226
285
|
geometryStatus: pixelX !== undefined && pixelY !== undefined
|
|
227
286
|
? 'source_pixel'
|
|
228
287
|
: placement.x !== undefined && placement.y !== undefined
|
|
@@ -1093,6 +1152,8 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1093
1152
|
const selectedFolders = activeDefinitions(fragments.flatMap(({ folders }) => folders));
|
|
1094
1153
|
const folders = localiseFolders(selectedFolders.active, snapshot.index);
|
|
1095
1154
|
const gridboxes = activeGridboxes(fragments.flatMap(({ gridboxes }) => gridboxes));
|
|
1155
|
+
const itemLayouts = activeItemLayouts(fragments.flatMap(({ itemLayouts }) => itemLayouts));
|
|
1156
|
+
const yearMarkers = activeYearMarkers(fragments.flatMap(({ yearMarkers }) => yearMarkers));
|
|
1096
1157
|
const categories = activeCategories(fragments.flatMap(({ categories }) => categories));
|
|
1097
1158
|
const selectedDoctrines = activeDefinitions(fragments.flatMap(({ doctrineDefinitions }) => doctrineDefinitions));
|
|
1098
1159
|
const doctrineDefinitions = localiseDoctrines(selectedDoctrines.active, snapshot.index, assets);
|
|
@@ -1102,8 +1163,8 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1102
1163
|
const unlockTargets = selectedTargets.active;
|
|
1103
1164
|
const basePlacements = selectOwnedRecords(fragments.flatMap(({ placements }) => placements), ({ technologyId }) => technologyId, ({ location }) => location, technologyById).sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
1104
1165
|
const edges = selectOwnedRecords(fragments.flatMap(({ edges }) => edges), ({ from }) => from, ({ location }) => location, technologyById).sort((left, right) => compareCodeUnits(left.id, right.id));
|
|
1105
|
-
const placements = enrichPlacements(basePlacements, edges, gridboxes, options.signal);
|
|
1106
1166
|
const unlocks = resolveUnlocks(selectOwnedRecords(fragments.flatMap(({ unlocks }) => unlocks), ({ technologyId }) => technologyId, ({ location }) => location, technologyById).sort((left, right) => compareCodeUnits(left.id, right.id)), unlockTargets, snapshot.complete);
|
|
1167
|
+
const placements = enrichPlacements(basePlacements, edges, gridboxes, technologies, unlocks, itemLayouts, options.signal);
|
|
1107
1168
|
const unresolved = fragments.flatMap(({ unresolved }) => unresolved);
|
|
1108
1169
|
const helperCalls = fragments.flatMap(({ helperCalls }) => helperCalls);
|
|
1109
1170
|
const externalReferences = expandHelperReferences(fragments.flatMap(({ externalReferences }) => externalReferences), helperCalls, unresolved, options.analysisMode ?? 'full', options.signal);
|
|
@@ -1167,6 +1228,8 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1167
1228
|
folders,
|
|
1168
1229
|
placements,
|
|
1169
1230
|
gridboxes,
|
|
1231
|
+
itemLayouts,
|
|
1232
|
+
yearMarkers,
|
|
1170
1233
|
edges,
|
|
1171
1234
|
categories,
|
|
1172
1235
|
doctrineDefinitions,
|
|
@@ -1183,6 +1246,8 @@ export function buildTechnologyGraph(snapshot, options) {
|
|
|
1183
1246
|
folderCount: folders.length,
|
|
1184
1247
|
placementCount: placements.length,
|
|
1185
1248
|
gridboxCount: gridboxes.length,
|
|
1249
|
+
itemLayoutCount: itemLayouts.length,
|
|
1250
|
+
yearMarkerCount: yearMarkers.length,
|
|
1186
1251
|
prerequisiteCount: edges.filter(({ kind }) => kind === 'prerequisite').length,
|
|
1187
1252
|
exclusiveCount: edges.filter(({ kind }) => kind === 'exclusive').length,
|
|
1188
1253
|
categoryCount: categories.length,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const TECHNOLOGY_GRAPH_SCHEMA_VERSION =
|
|
1
|
+
export const TECHNOLOGY_GRAPH_SCHEMA_VERSION = 2;
|
|
2
2
|
export const TECHNOLOGY_PARSER_VERSION = 'clausewitz-cst.v1';
|
|
@@ -15,6 +15,8 @@ function technologyRecordCount(graph) {
|
|
|
15
15
|
graph.categories.length +
|
|
16
16
|
graph.placements.length +
|
|
17
17
|
graph.gridboxes.length +
|
|
18
|
+
graph.itemLayouts.length +
|
|
19
|
+
graph.yearMarkers.length +
|
|
18
20
|
graph.edges.length +
|
|
19
21
|
graph.unlocks.length +
|
|
20
22
|
graph.unlockTargets.length +
|
|
@@ -62,6 +64,8 @@ export function technologyScanReport(graph, fullGraphRecordLimit = TECHNOLOGY_SC
|
|
|
62
64
|
categories: graph.categories.length,
|
|
63
65
|
placements: graph.placements.length,
|
|
64
66
|
gridboxes: graph.gridboxes.length,
|
|
67
|
+
itemLayouts: graph.itemLayouts.length,
|
|
68
|
+
yearMarkers: graph.yearMarkers.length,
|
|
65
69
|
edges: graph.edges.length,
|
|
66
70
|
unlocks: graph.unlocks.length,
|
|
67
71
|
unlockTargets: graph.unlockTargets.length,
|
|
@@ -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.0.
|
|
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;
|
|
Binary file
|