hoi4-agent-tools 2.5.3 → 2.6.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.
Files changed (33) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +5 -5
  3. package/dist/hoi4_agent_tools/map/catalog.js +398 -0
  4. package/dist/hoi4_agent_tools/map/index.js +1 -0
  5. package/dist/hoi4_agent_tools/map/operations.js +337 -30
  6. package/dist/hoi4_agent_tools/map/render.js +40 -147
  7. package/dist/hoi4_agent_tools/map/service.js +1 -0
  8. package/dist/hoi4_agent_tools/mcp/tools/map.js +85 -9
  9. package/dist/hoi4_agent_tools/schemas/map.js +69 -2
  10. package/dist/hoi4_agent_tools/version.js +1 -1
  11. package/docs/map.md +60 -23
  12. package/package.json +1 -1
  13. package/schemas/configuration.schema.json +1 -1
  14. package/schemas/continuous-focus-palette.schema.json +1 -1
  15. package/schemas/custom-weighted-pool.schema.json +1 -1
  16. package/schemas/event-feature-manifest.schema.json +1 -1
  17. package/schemas/focus-plan.schema.json +1 -1
  18. package/schemas/focus-planning-sidecar.schema.json +1 -1
  19. package/schemas/gui-animation-source.schema.json +1 -1
  20. package/schemas/gui-helper.schema.json +1 -1
  21. package/schemas/gui-scenario.schema.json +1 -1
  22. package/schemas/map-operation.schema.json +228 -74
  23. package/schemas/operation-result.schema.json +1 -1
  24. package/schemas/probability-analysis-result.schema.json +1 -1
  25. package/schemas/probability-compare-input.schema.json +1 -1
  26. package/schemas/probability-evaluate-input.schema.json +1 -1
  27. package/schemas/probability-inspect-input.schema.json +1 -1
  28. package/schemas/probability-render-input.schema.json +1 -1
  29. package/schemas/probability-scenario-set.schema.json +1 -1
  30. package/schemas/probability-sequence-input.schema.json +1 -1
  31. package/schemas/probability-simulate-input.schema.json +1 -1
  32. package/schemas/probability-sweep-input.schema.json +1 -1
  33. package/server.json +2 -2
package/CHANGELOG.md CHANGED
@@ -4,6 +4,12 @@
4
4
 
5
5
  No unreleased changes.
6
6
 
7
+ ## 2.6.0 - 2026-08-22
8
+
9
+ - Added a shared complete map catalog with resolved names, source locations, geometry, memberships, networks, positions, buildings, ports, adjacency, and localisation evidence.
10
+ - Made map inspection return a full-map overview and a searchable, clickable HTML navigator with exact province hit testing and pixel/map coordinate lookup.
11
+ - Added compact state and province creation defaults, rectangle province geometry, and one-call province, state, and strategic-region ID swapping with connected map-reference and standard localisation updates.
12
+
7
13
  ## 2.5.3 - 2026-08-22
8
14
 
9
15
  - Resolved and embedded technology icons through the complete mod, dependency, and installed-game load order, including vanilla sprites used by mod content.
package/README.md CHANGED
@@ -6,7 +6,7 @@ HOI4 Agent Tools is an MCP server for coding agents to understand Hearts of Iron
6
6
 
7
7
  - Focus trees: inspect structure and references, render layouts, create trees, and reorganize existing branches.
8
8
  - Scripted GUIs: trace GUI, GFX, scripted-GUI, and localisation links; render value-driven variants, states, resolutions, hierarchy, and click regions; diagnose alignment, visibility, clipping, panel containment, and button-label centering; create or repair interface source.
9
- - Maps: inspect provinces, states, regions, adjacency, supply, and railways; render layers; create and repair exact map data.
9
+ - Maps: navigate the complete rendered map by ID or localised name; inspect provinces, states, regions, adjacency, supply, railways, and positions; create states and provinces; change IDs; and repair connected map data.
10
10
  - Event chains: scan definitions and call sites, trace routes and state flow, lint references, render graphs, and compare revisions without editing event source.
11
11
  - Technology trees: reconstruct technology and doctrine paths, folder layouts, unlocks, bonuses, grants, metadata, assets, and structural changes.
12
12
  - AI and MTTH: evaluate weighted choices and timing across explicit scenarios, sweep uncertain inputs, simulate distributions, compare patches, and analyze declared stateful pools.
@@ -40,9 +40,9 @@ Paste the printed Codex or generic global-install entry into your MCP client, th
40
40
  | `hoi4.gui_inspect` | Read a scripted GUI and its linked assets and logic. |
41
41
  | `hoi4.gui_render` | Render GUI scenario variants, states, resolutions, hierarchy, and layout diagnostics. |
42
42
  | `hoi4.gui_rewrite` | Create or update a GUI source package. |
43
- | `hoi4.map_inspect` | Read map, state, province, region, supply, and railway data. |
44
- | `hoi4.map_render` | Render map layers and overlays. |
45
- | `hoi4.map_rewrite` | Create or update map data from an ordered list of exact changes. |
43
+ | `hoi4.map_inspect` | Search, click, navigate, and inspect the complete rendered map and its linked data. |
44
+ | `hoi4.map_render` | Render full-map layers, overlays, names, IDs, coordinates, and source-linked catalogs. |
45
+ | `hoi4.map_rewrite` | Create or update states, provinces, IDs, networks, positions, and connected map data. |
46
46
  | `hoi4.event_inspect` | Scan, trace, explain, lint, or assess event chains and their state flow. |
47
47
  | `hoi4.event_render` | Render source-linked event routes, options, timing, state, scope, and unresolved edges. |
48
48
  | `hoi4.event_compare` | Compare event-chain topology and diagnostics between revisions. |
@@ -71,7 +71,7 @@ Ask your agent in normal task language. A typical workflow is inspect, render, r
71
71
 
72
72
  - Focus trees: "Create a complete national focus tree for this route specification," or "Compact this existing tree into a balanced, readable layout." Existing trees can use a plan-free compact reflow; new trees use a complete plan. See [Focus trees](docs/focus.md).
73
73
  - Scripted GUIs: "Create a scripted GUI for this mechanic," or "Render every value-driven version of this window and fix hidden controls, off-center button text, background alignment, clipping, and click-region conflicts." See [Scripted GUIs](docs/gui.md).
74
- - Maps: "Create a state from these exact provinces," or "Inspect this state and split these provinces while keeping supply and railway references valid." See [Maps](docs/map.md).
74
+ - Maps: "Render the whole map and find this state by name," "Create a state from these provinces," "Create a province inside this exact rectangle," or "Swap these state IDs and update connected references." See [Maps](docs/map.md).
75
75
  - Event chains: "Trace every route from this event and explain where its flags and variables change," or "Compare the workspace event graph with its previous revision and render the affected routes." See [Event chains](docs/events.md).
76
76
  - Technology trees: "Explain everything this technology requires and unlocks," or "Compare this technology patch and render every affected folder and doctrine branch." See [Technology trees](docs/technology.md).
77
77
  - AI and MTTH: "Compare these focus weights across peace, defensive-war, and low-stability scenarios," or "Show when this MTTH event becomes likely and which unknown inputs control the result." See [AI and MTTH analysis](docs/probability.md).
@@ -0,0 +1,398 @@
1
+ import { compareCodeUnits } from '../core/canonical.js';
2
+ import { nodeLocation } from '../core/source/parser.js';
3
+ function localisedValues(entries) {
4
+ return entries
5
+ .map(({ file, entry }) => ({
6
+ language: entry.language,
7
+ value: entry.value,
8
+ sourcePath: file.displayPath,
9
+ line: entry.line,
10
+ loadOrder: file.loadOrder,
11
+ }))
12
+ .sort((left, right) => compareCodeUnits(left.language, right.language) ||
13
+ left.loadOrder - right.loadOrder ||
14
+ compareCodeUnits(left.sourcePath, right.sourcePath) ||
15
+ left.line - right.line)
16
+ .map(({ loadOrder: _loadOrder, ...value }) => value);
17
+ }
18
+ function resolvedName(entriesByKey, keys, fallback) {
19
+ for (const key of keys) {
20
+ const values = localisedValues(entriesByKey.get(key) ?? []);
21
+ if (values.length === 0)
22
+ continue;
23
+ const english = values.filter(({ language }) => language === 'l_english').at(-1);
24
+ return { key, displayName: english?.value ?? values.at(-1)?.value ?? fallback, values };
25
+ }
26
+ const key = keys[0] ?? fallback;
27
+ return { key, displayName: fallback, values: [] };
28
+ }
29
+ function aggregateGeometry(index, provinceIds) {
30
+ const geometries = provinceIds.flatMap((id) => {
31
+ const geometry = index.raster?.geometry.get(id);
32
+ return geometry === undefined ? [] : [geometry];
33
+ });
34
+ if (geometries.length === 0 || index.raster === undefined)
35
+ return null;
36
+ const pixelCount = geometries.reduce((total, { pixelCount: count }) => total + count, 0);
37
+ if (pixelCount <= 0)
38
+ return null;
39
+ const centerX = geometries.reduce((total, geometry) => total + geometry.centerX * geometry.pixelCount, 0) /
40
+ pixelCount;
41
+ const centerY = geometries.reduce((total, geometry) => total + geometry.centerY * geometry.pixelCount, 0) /
42
+ pixelCount;
43
+ return {
44
+ pixelCount,
45
+ minX: geometries.reduce((value, geometry) => Math.min(value, geometry.minX), Infinity),
46
+ minY: geometries.reduce((value, geometry) => Math.min(value, geometry.minY), Infinity),
47
+ maxX: geometries.reduce((value, geometry) => Math.max(value, geometry.maxX), -Infinity),
48
+ maxY: geometries.reduce((value, geometry) => Math.max(value, geometry.maxY), -Infinity),
49
+ centerX,
50
+ centerY,
51
+ mapX: centerX,
52
+ mapZ: index.raster.height - 1 - centerY,
53
+ };
54
+ }
55
+ function sortedObject(values) {
56
+ return Object.fromEntries([...values].sort(([left], [right]) => compareCodeUnits(left, right)));
57
+ }
58
+ export function buildMapCatalog(index) {
59
+ const raster = index.raster;
60
+ const width = raster?.width ?? 0;
61
+ const height = raster?.height ?? 0;
62
+ const localisationEntriesByKey = new Map();
63
+ for (const entry of index.localisationEntries) {
64
+ const values = localisationEntriesByKey.get(entry.entry.key) ?? [];
65
+ values.push(entry);
66
+ localisationEntriesByKey.set(entry.entry.key, values);
67
+ }
68
+ const portsByProvince = new Map(index.ports.map((port) => [port.provinceId, port]));
69
+ const supplyNodeLevelsByProvince = new Map();
70
+ for (const node of index.supplyNodes) {
71
+ const levels = supplyNodeLevelsByProvince.get(node.provinceId) ?? [];
72
+ levels.push(node.level);
73
+ supplyNodeLevelsByProvince.set(node.provinceId, levels);
74
+ }
75
+ const buildingPositionsByProvince = new Map();
76
+ for (const [ordinal, position] of index.buildingPositions.entries()) {
77
+ const provinceId = index.provinceAtMapCoordinate(position.x, position.z);
78
+ if (provinceId === undefined)
79
+ continue;
80
+ const ordinals = buildingPositionsByProvince.get(provinceId) ?? [];
81
+ ordinals.push(ordinal);
82
+ buildingPositionsByProvince.set(provinceId, ordinals);
83
+ }
84
+ const unitPositionsByProvince = new Map();
85
+ for (const [ordinal, position] of index.unitPositions.entries()) {
86
+ const ordinals = unitPositionsByProvince.get(position.provinceId) ?? [];
87
+ ordinals.push(ordinal);
88
+ unitPositionsByProvince.set(position.provinceId, ordinals);
89
+ }
90
+ const railwayOrdinalsByProvince = new Map();
91
+ for (const [ordinal, railway] of index.railways.entries()) {
92
+ for (const provinceId of new Set(railway.provinces)) {
93
+ const ordinals = railwayOrdinalsByProvince.get(provinceId) ?? [];
94
+ ordinals.push(ordinal);
95
+ railwayOrdinalsByProvince.set(provinceId, ordinals);
96
+ }
97
+ }
98
+ const specialAdjacencyOrdinalsByProvince = new Map();
99
+ for (const [ordinal, adjacency] of index.adjacencies.entries()) {
100
+ for (const provinceId of new Set([adjacency.from, adjacency.to, adjacency.through])) {
101
+ if (provinceId < 0)
102
+ continue;
103
+ const ordinals = specialAdjacencyOrdinalsByProvince.get(provinceId) ?? [];
104
+ ordinals.push(ordinal);
105
+ specialAdjacencyOrdinalsByProvince.set(provinceId, ordinals);
106
+ }
107
+ }
108
+ const provinces = [...index.definitions]
109
+ .sort((left, right) => left.id - right.id)
110
+ .map((definition) => {
111
+ const id = definition.id;
112
+ const port = portsByProvince.get(id);
113
+ return {
114
+ kind: 'province',
115
+ id,
116
+ name: resolvedName(localisationEntriesByKey, [`PROV${id}`, `VICTORY_POINTS_${id}`], `Province ${id}`),
117
+ definition: {
118
+ color: definition.color,
119
+ type: definition.type,
120
+ coastal: definition.coastal,
121
+ terrain: definition.terrain,
122
+ continent: definition.continent,
123
+ sourcePath: definition.document.file.displayPath,
124
+ line: definition.line,
125
+ },
126
+ geometry: aggregateGeometry(index, [id]),
127
+ stateIds: index.stateForProvince(id).map(({ id: stateId }) => stateId),
128
+ regionIds: index.regionForProvince(id).map(({ id: regionId }) => regionId),
129
+ neighborProvinceIds: [...(raster?.adjacency.get(id) ?? [])].sort((a, b) => a - b),
130
+ victoryPoints: index.victoryPointsByProvince
131
+ .get(id)
132
+ ?.map(({ stateId, value }) => ({ stateId, value })) ?? [],
133
+ provinceBuildings: index.provinceBuildingsByProvince.get(id)?.map(({ stateId, buildings }) => ({
134
+ stateId,
135
+ buildings: sortedObject(buildings),
136
+ })) ?? [],
137
+ port: port === undefined
138
+ ? null
139
+ : {
140
+ stateId: port.stateId,
141
+ level: port.level,
142
+ coastal: port.coastal,
143
+ adjacentSeaProvinceIds: [...port.adjacentSeaProvinceIds],
144
+ },
145
+ supplyNodeLevels: supplyNodeLevelsByProvince.get(id) ?? [],
146
+ railwayOrdinals: railwayOrdinalsByProvince.get(id) ?? [],
147
+ specialAdjacencyOrdinals: specialAdjacencyOrdinalsByProvince.get(id) ?? [],
148
+ buildingPositionOrdinals: buildingPositionsByProvince.get(id) ?? [],
149
+ unitPositionOrdinals: unitPositionsByProvince.get(id) ?? [],
150
+ };
151
+ });
152
+ const states = [...index.states]
153
+ .sort((left, right) => left.id - right.id)
154
+ .map((state) => ({
155
+ kind: 'state',
156
+ id: state.id,
157
+ name: resolvedName(localisationEntriesByKey, [state.name], state.name),
158
+ geometry: aggregateGeometry(index, state.provinces),
159
+ capital: state.capital ?? null,
160
+ manpower: state.manpower,
161
+ category: state.category,
162
+ provinceIds: [...state.provinces].sort((a, b) => a - b),
163
+ regionIds: [
164
+ ...new Set(state.provinces.flatMap((provinceId) => index.regionForProvince(provinceId).map(({ id }) => id))),
165
+ ].sort((a, b) => a - b),
166
+ resources: sortedObject(state.resources),
167
+ owner: state.owner ?? null,
168
+ controller: state.controller ?? null,
169
+ cores: [...state.cores].sort(compareCodeUnits),
170
+ claims: [...state.claims].sort(compareCodeUnits),
171
+ victoryPoints: state.victoryPoints
172
+ .map(({ provinceId, value }) => ({ provinceId, value }))
173
+ .sort((left, right) => left.provinceId - right.provinceId || left.value - right.value),
174
+ stateBuildings: sortedObject(state.stateBuildings),
175
+ provinceBuildings: Object.fromEntries([...state.provinceBuildings]
176
+ .sort(([left], [right]) => left - right)
177
+ .map(([provinceId, buildings]) => [String(provinceId), sortedObject(buildings)])),
178
+ sourcePath: state.file.displayPath,
179
+ sourceLine: nodeLocation(state.document, state.assignment).start.line,
180
+ }));
181
+ const strategicRegions = [...index.regions]
182
+ .sort((left, right) => left.id - right.id)
183
+ .map((region) => ({
184
+ kind: 'strategic-region',
185
+ id: region.id,
186
+ name: resolvedName(localisationEntriesByKey, [region.name], region.name),
187
+ geometry: aggregateGeometry(index, region.provinces),
188
+ provinceIds: [...region.provinces].sort((a, b) => a - b),
189
+ stateIds: [
190
+ ...new Set(region.provinces.flatMap((provinceId) => index.stateForProvince(provinceId).map(({ id }) => id))),
191
+ ].sort((a, b) => a - b),
192
+ navalTerrain: region.navalTerrain ?? null,
193
+ sourcePath: region.file.displayPath,
194
+ sourceLine: nodeLocation(region.document, region.assignment).start.line,
195
+ }));
196
+ const normalAdjacencies = [...(raster?.adjacency ?? [])]
197
+ .flatMap(([from, neighbors]) => [...neighbors].flatMap((to) => (from < to ? [{ from, to }] : [])))
198
+ .sort((left, right) => left.from - right.from || left.to - right.to);
199
+ return {
200
+ schemaVersion: 2,
201
+ dimensions: { width, height },
202
+ coordinateSystems: {
203
+ pixel: { origin: 'top-left', xDirection: 'right', yDirection: 'down' },
204
+ map: { origin: 'bottom-left', xDirection: 'right', zDirection: 'up' },
205
+ },
206
+ counts: {
207
+ provinces: provinces.length,
208
+ states: states.length,
209
+ strategicRegions: strategicRegions.length,
210
+ normalAdjacencies: normalAdjacencies.length,
211
+ specialAdjacencies: index.adjacencies.length,
212
+ supplyNodes: index.supplyNodes.length,
213
+ railways: index.railways.length,
214
+ ports: index.ports.length,
215
+ victoryPoints: [...index.victoryPointsByProvince.values()].reduce((count, entries) => count + entries.length, 0),
216
+ buildingPositions: index.buildingPositions.length,
217
+ unitPositions: index.unitPositions.length,
218
+ weatherPositions: index.weatherPositions.length,
219
+ entityLocators: index.entityLocators.length,
220
+ },
221
+ provinces,
222
+ states,
223
+ strategicRegions,
224
+ normalAdjacencies,
225
+ specialAdjacencies: index.adjacencies.map((adjacency, ordinal) => ({
226
+ ordinal,
227
+ from: adjacency.from,
228
+ to: adjacency.to,
229
+ type: adjacency.type,
230
+ through: adjacency.through,
231
+ startX: adjacency.startX,
232
+ startY: adjacency.startY,
233
+ stopX: adjacency.stopX,
234
+ stopY: adjacency.stopY,
235
+ rule: adjacency.rule,
236
+ comment: adjacency.comment,
237
+ sourcePath: adjacency.document.file.displayPath,
238
+ line: adjacency.line,
239
+ })),
240
+ supplyNodes: index.supplyNodes.map((node, ordinal) => ({
241
+ ordinal,
242
+ level: node.level,
243
+ provinceId: node.provinceId,
244
+ sourcePath: node.document.file.displayPath,
245
+ line: node.line,
246
+ })),
247
+ railways: index.railways.map((railway, ordinal) => ({
248
+ ordinal,
249
+ level: railway.level,
250
+ declaredCount: railway.declaredCount,
251
+ provinceIds: [...railway.provinces],
252
+ sourcePath: railway.document.file.displayPath,
253
+ line: railway.line,
254
+ })),
255
+ ports: index.ports.map(({ stateId, provinceId, level, coastal, adjacentSeaProvinceIds }) => ({
256
+ stateId,
257
+ provinceId,
258
+ level,
259
+ coastal,
260
+ adjacentSeaProvinceIds: [...adjacentSeaProvinceIds].sort((left, right) => left - right),
261
+ })),
262
+ victoryPoints: [...index.victoryPointsByProvince.values()]
263
+ .flatMap((entries) => entries.map(({ stateId, provinceId, value }) => ({ stateId, provinceId, value })))
264
+ .sort((left, right) => left.stateId - right.stateId ||
265
+ left.provinceId - right.provinceId ||
266
+ left.value - right.value),
267
+ buildingPositions: index.buildingPositions.map((position, ordinal) => ({
268
+ ordinal,
269
+ stateId: position.stateId,
270
+ building: position.building,
271
+ x: position.x,
272
+ y: position.y,
273
+ z: position.z,
274
+ rotation: position.rotation,
275
+ adjacentSeaProvince: position.adjacentSeaProvince,
276
+ sourcePath: position.document.file.displayPath,
277
+ line: position.line,
278
+ })),
279
+ unitPositions: index.unitPositions.map((position, ordinal) => ({
280
+ ordinal,
281
+ provinceId: position.provinceId,
282
+ type: position.type,
283
+ x: position.x,
284
+ y: position.y,
285
+ z: position.z,
286
+ rotation: position.rotation,
287
+ offset: position.offset,
288
+ sourcePath: position.document.file.displayPath,
289
+ line: position.line,
290
+ })),
291
+ weatherPositions: index.weatherPositions.map((position, ordinal) => ({
292
+ ordinal,
293
+ strategicRegionId: position.strategicRegionId,
294
+ x: position.x,
295
+ y: position.y,
296
+ z: position.z,
297
+ size: position.size,
298
+ sourcePath: position.document.file.displayPath,
299
+ line: position.line,
300
+ })),
301
+ entityLocators: [...index.entityLocators]
302
+ .sort((left, right) => compareCodeUnits(left.entity, right.entity) || compareCodeUnits(left.name, right.name))
303
+ .map(({ entity, name, position, file }) => ({
304
+ entity,
305
+ name,
306
+ position: [...position],
307
+ sourcePath: file.displayPath,
308
+ })),
309
+ };
310
+ }
311
+ function searchScore(query, id, key, displayName, alternatives) {
312
+ const idText = String(id);
313
+ const normalizedKey = key.toLocaleLowerCase('en-US');
314
+ const normalizedName = displayName.toLocaleLowerCase('en-US');
315
+ const normalizedAlternatives = alternatives.map((value) => value.toLocaleLowerCase('en-US'));
316
+ if (query === idText ||
317
+ query === normalizedKey ||
318
+ query === normalizedName ||
319
+ normalizedAlternatives.includes(query))
320
+ return 0;
321
+ if (idText.startsWith(query) ||
322
+ normalizedKey.startsWith(query) ||
323
+ normalizedName.startsWith(query) ||
324
+ normalizedAlternatives.some((value) => value.startsWith(query)))
325
+ return 1;
326
+ if (normalizedKey.includes(query) ||
327
+ normalizedName.includes(query) ||
328
+ normalizedAlternatives.some((value) => value.includes(query)))
329
+ return 2;
330
+ return undefined;
331
+ }
332
+ export function searchMapCatalog(catalog, query, limit = 100) {
333
+ const normalized = query.trim().toLocaleLowerCase('en-US');
334
+ if (normalized === '')
335
+ return [];
336
+ const entries = [
337
+ ...catalog.provinces.map(({ id, name, geometry }) => ({
338
+ kind: 'province',
339
+ id,
340
+ key: name.key,
341
+ displayName: name.displayName,
342
+ alternatives: name.values.map(({ value }) => value),
343
+ geometry,
344
+ })),
345
+ ...catalog.states.map(({ id, name, geometry }) => ({
346
+ kind: 'state',
347
+ id,
348
+ key: name.key,
349
+ displayName: name.displayName,
350
+ alternatives: name.values.map(({ value }) => value),
351
+ geometry,
352
+ })),
353
+ ...catalog.strategicRegions.map(({ id, name, geometry }) => ({
354
+ kind: 'strategic-region',
355
+ id,
356
+ key: name.key,
357
+ displayName: name.displayName,
358
+ alternatives: name.values.map(({ value }) => value),
359
+ geometry,
360
+ })),
361
+ ];
362
+ return entries
363
+ .flatMap((entry) => {
364
+ const score = searchScore(normalized, entry.id, entry.key, entry.displayName, entry.alternatives);
365
+ if (score === undefined)
366
+ return [];
367
+ const { alternatives: _alternatives, ...result } = entry;
368
+ return [{ ...result, score }];
369
+ })
370
+ .sort((left, right) => left.score - right.score || compareCodeUnits(left.kind, right.kind) || left.id - right.id)
371
+ .slice(0, Math.max(0, limit));
372
+ }
373
+ export function lookupMapCoordinate(index, coordinate) {
374
+ const raster = index.raster;
375
+ const pixel = coordinate.kind === 'pixel'
376
+ ? { x: Math.floor(coordinate.x), y: Math.floor(coordinate.y) }
377
+ : raster === undefined
378
+ ? null
379
+ : { x: Math.floor(coordinate.x), y: raster.height - 1 - Math.floor(coordinate.z) };
380
+ if (raster === undefined ||
381
+ pixel === null ||
382
+ pixel.x < 0 ||
383
+ pixel.y < 0 ||
384
+ pixel.x >= raster.width ||
385
+ pixel.y >= raster.height) {
386
+ return { coordinate, pixel: null, provinceId: null, stateIds: [], regionIds: [] };
387
+ }
388
+ const provinceId = raster.provinceIds[pixel.y * raster.width + pixel.x] ?? -1;
389
+ if (provinceId < 0)
390
+ return { coordinate, pixel, provinceId: null, stateIds: [], regionIds: [] };
391
+ return {
392
+ coordinate,
393
+ pixel,
394
+ provinceId,
395
+ stateIds: index.stateForProvince(provinceId).map(({ id }) => id),
396
+ regionIds: index.regionForProvince(provinceId).map(({ id }) => id),
397
+ };
398
+ }
@@ -1,4 +1,5 @@
1
1
  export * from './bmp.js';
2
+ export * from './catalog.js';
2
3
  export * from './limits.js';
3
4
  export * from './model.js';
4
5
  export * from './operations.js';